You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Mar 6, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Mar 6, 2026. It is now read-only.
The support for python Bigquery API indicates that arrays are possible, however, when passing from a pandas dataframe to bigquery there is a pyarrow struct issue.
The only way round it seems its to drop columns then use JSON Normalise for a separate table.
Loading a dataframe containing an array (a Python type List) does work if you don't specific the schema in the LoadJobConfig() and just let it detect it with autodetect=True. You just get a warning from pyarrow: UserWarning: Pyarrow could not determine the type of columns: COLUMN_NAME. ", ".join(field.name for field in unknown_type_fields)
There is no way to provide a schema with the ARRAY type though, which is a bit frustrating. Or even RECORD or STRUCT type.
FWIW, uploading STRUCT columns through a dataframe will become possible when a fix in the pyarrow dependency will be released. I tried that with the development pyarrow version and it worked.
At least uploading REPEATED fields (even those consisting of STRUCTs) works with the load_table_from_json() method, if that workaround is feasible. I managed to successfully upload the following data with it:
As far as I can tell, the use case of loading a dataframe that contains an array (no struct) doesn't work even with the new version of Pyarrow (0.17.1).
@aaaaahaaaaa A use case of loading a dataframe that contains an array is not supported by bigquery and raised an error Field is specified as REPEATED in provided schema which does not match REQUIRED as specified in the file, it is due to dataframe to parquest file conversion and for more information you can see #17 old issue related to that.
Parquet limitation note for REPEATED fields has added and you could found in documentation of load_table_from_dataframe when BigQuery's 1.26.0 version will release.
Instead of using load_table_from_dataframe you can use load_table_from_json
So just to confirm my read here, am I correct in saying:
There is no way to use a REPEATED type with the load_table_from_dataframe function right now. This is due to how parquet serializes dataframes which is ultimately how this library serializes frames for transfer to bigquery. The current workaround is to use load_table_from_json. I put together a test to showcase how different methods breakdown here (run with code at master af2c987):
import sys
import pytest
import typing
import pandas as pd
from google.cloud.bigquery import Client, schema, job
T = typing.TypeVar("T")
def construct_repeated(
lst: typing.List[T],
) -> typing.Mapping[str, typing.List[typing.Mapping[str, T]]]:
""" when bigquery reads parquet arrays it ultimately structures the schema to look like this struct<list array<struct<item i>>> """
return {"list": [{"item": obj} for obj in lst]}
def destruct_repeated(
dct: typing.Mapping[str, typing.List[typing.Mapping[str, T]]]
) -> typing.List[T]:
return [d["item"] for d in dct["list"]]
def apply_col(
col: str, callable: typing.Callable[[T], typing.Any]
) -> typing.Callable[[pd.DataFrame], pd.DataFrame]:
def wrapper(df: pd.DataFrame) -> pd.DataFrame:
df[col] = df[col].apply(callable)
return df
return wrapper
repeated_df = pd.DataFrame({"id": ["a", "b"], "targets": [[1], [2, 3]]})
repeated_schema = [
schema.SchemaField("id", "STRING", "NULLABLE", None, (), None),
schema.SchemaField("targets", "INTEGER", "REPEATED", None, (), None),
]
nested_df = repeated_df.copy()
nested_df["targets"] = nested_df["targets"].apply(construct_repeated)
nested_schema = [
schema.SchemaField("id", "STRING", "NULLABLE", None, (), None),
schema.SchemaField(
"targets",
"RECORD",
"NULLABLE",
None,
(
schema.SchemaField(
"list",
"RECORD",
"REPEATED",
None,
(schema.SchemaField("item", "INTEGER", "NULLABLE", None, (), None),),
None,
),
),
None,
),
]
@pytest.fixture
def client():
client = Client()
yield client
client.close()
@pytest.fixture
def dataset(client):
d = client.create_dataset("test_dataset")
yield d
client.delete_dataset(d)
@pytest.fixture
def table(client, dataset):
table = dataset.table("test_table")
yield table
client.delete_table(table, not_found_ok=True)
@pytest.mark.parametrize(
"df, schema, post",
[
# FAILS WITH:
# google.api_core.exceptions.BadRequest: 400 Error while reading data,
# error message: Provided schema is not compatible with the file
# 'prod-scotty-81de9f48-1a4f-424e-be99-2258b706fc46'. Field 'targets' is
# specified as REPEATED in provided schema which does not match REQUIRED as
# specified in the file.
(repeated_df, repeated_schema, None),
# FAILS comparing `df.to_dict() == got_back.to_dict()`:
# doesn't upload structs properly, got_back ->
# id targets
# 0 a None
# 1 b None
(nested_df, nested_schema, None),
],
)
def test_roundtrip_from_dataframe(df, schema, post, client, table):
client.load_table_from_dataframe(
df, table, job_config=job.LoadJobConfig(schema=schema)
).result()
got_back = client.list_rows(table).to_dataframe()
if post is not None:
got_back = post(got_back)
assert df.to_dict() == got_back.to_dict()
@pytest.mark.parametrize(
"df, schema, post",
[
# FAILS WITH:
# google.api_core.exceptions.BadRequest: 400 Error while reading data,
# error message: Provided schema is not compatible with the file
# 'prod-scotty-81de9f48-1a4f-424e-be99-2258b706fc46'. Field 'targets' is
# specified as REPEATED in provided schema which does not match REQUIRED as
# specified in the file.
(repeated_df, repeated_schema, None),
# FAILS comparing `df.to_dict() == got_back.to_dict()`:
# doesn't upload structs properly, got_back ->
# id targets
# 0 a None
# 1 b None
(nested_df, nested_schema, None),
],
)
def test_roundtrip_from_dataframe_existing_table(df, schema, post, client, table):
table.schema = schema
client.create_table(table)
client.load_table_from_dataframe(df, table).result()
got_back = client.list_rows(table).to_dataframe()
if post is not None:
got_back = post(got_back)
assert df.to_dict() == got_back.to_dict()
@pytest.mark.parametrize(
"df, schema, post",
[
# works but effectively applies construct_repeated on targets elements
(repeated_df, repeated_schema, apply_col("targets", destruct_repeated)),
],
)
def test_roundtrip_from_dataframe_no_schema(df, schema, post, client, table):
client.load_table_from_dataframe(df, table).result()
got_back = client.list_rows(table).to_dataframe()
if post is not None:
got_back = post(got_back)
assert df.to_dict() == got_back.to_dict()
@pytest.mark.parametrize(
"df, schema, post",
[
# to_dataframe here returns an `np.array` type for lists which you can't compare
# with a `list` type so we convert them for comparison
(repeated_df, repeated_schema, apply_col("targets", list)),
(nested_df, nested_schema, apply_col("targets", lambda d: {"list": list(d['list'])})),
],
)
def test_roundtrip_from_json(df, schema, post, client, table):
client.load_table_from_json(
df.to_dict(orient="records"), table, job_config=job.LoadJobConfig(schema=schema)
).result()
got_back = client.list_rows(table).to_dataframe()
if post is not None:
got_back = post(got_back)
assert df.to_dict() == got_back.to_dict()
if __name__ == "__main__":
pytest.main([__file__, *sys.argv[1:]])
^ this is true but it only partly resolves this issue or at least the specific complaint "It still serializes to a strange format with an "item" column.")
This is great, thanks @judahrand! Since as you point out the list_inference param is already exposed, I guess all that's needed to close this ticket is adding use_compliant_nested_type=True in
Activity
tswast commentedon Jul 1, 2019
Thanks for the report. Struct support is reported at https://github.com/googleapis/google-cloud-python/issues/8191, but I'll keep this issue open as a feature request for arrays of scalar types.
[-]Arrays not supported in Bigquery Python API[/-][+]BigQuery: Upload pandas DataFrame containing arrays[/+]sumit-ql commentedon Sep 23, 2019
I am not able to repro this issue with dataframe containing arrays only. Its not giving any exception.
i can see an exception when we use dict(STRUCT/RECORD) in dataframe.
@AETDDraper Could you please provide a sample dataframe containing arrays which causes the error.
google-cloud-bigquery 1.20.0
pyarrow 0.14.0
pandas 0.25.1
JonathanBonnaud commentedon Mar 3, 2020
Loading a dataframe containing an array (a Python type List) does work if you don't specific the schema in the
LoadJobConfig()and just let it detect it withautodetect=True. You just get a warning from pyarrow:UserWarning: Pyarrow could not determine the type of columns: COLUMN_NAME. ", ".join(field.name for field in unknown_type_fields)The detected schema looks like this:
There is no way to provide a schema with the ARRAY type though, which is a bit frustrating. Or even RECORD or STRUCT type.
My config:
plamut commentedon Apr 2, 2020
FWIW, uploading STRUCT columns through a dataframe will become possible when a fix in the
pyarrowdependency will be released. I tried that with the developmentpyarrowversion and it worked.At least uploading REPEATED fields (even those consisting of STRUCTs) works with the
load_table_from_json()method, if that workaround is feasible. I managed to successfully upload the following data with it:aaaaahaaaaa commentedon May 20, 2020
As far as I can tell, the use case of loading a dataframe that contains an array (no struct) doesn't work even with the new version of Pyarrow (0.17.1).
Dataframe looks like this:
Loading the dataframe:
Produces:
HemangChothani commentedon Jun 25, 2020
@aaaaahaaaaa A use case of loading a dataframe that contains an array is not supported by bigquery and raised an error
Field is specified as REPEATED in provided schema which does not match REQUIRED as specified in the file, it is due to dataframe to parquest file conversion and for more information you can see #17 old issue related to that.Parquet limitation note for
REPEATEDfields has added and you could found in documentation ofload_table_from_dataframewhenBigQuery's 1.26.0version will release.Instead of using load_table_from_dataframe you can use load_table_from_json
example:
abuckenheimer commentedon Aug 7, 2020
So just to confirm my read here, am I correct in saying:
There is no way to use a
REPEATEDtype with theload_table_from_dataframefunction right now. This is due to how parquet serializes dataframes which is ultimately how this library serializes frames for transfer to bigquery. The current workaround is to useload_table_from_json. I put together a test to showcase how different methods breakdown here (run with code at master af2c987):tswast commentedon Nov 2, 2020
Pyarrow 2.0 released with improvements to the Parquet serialization. We should revisit to see if this issue can be resolved with pyarrow 2.0
HemangChothani commentedon Nov 3, 2020
@tswast I have tried with these two following examples and it's working fine.
Example 1:
Example 2:
tswast commentedon Nov 3, 2020
Let's add some system test cases and/or code samples for this (which are skipped unless we have pyarrow >=2.0) before we close this issue out.
I imagine it'd be useful to have samples which show more complex dataframes in the following docs:
tswast commentedon Nov 4, 2020
Based on the schema in #365, this feature is not yet supported. It still serializes to a strange format with an "item" column.
emkornfield commentedon Apr 29, 2021
We should look into how to integrate: --parquet_enable_list_inference from BQ CLI
judahrand commentedon Sep 20, 2021
We can already use that flag:
bnaul commentedon Sep 20, 2021
^ this is true but it only partly resolves this issue or at least the specific complaint "It still serializes to a strange format with an "item" column.")
enable_list_inference=False:enable_list_inference=True:emkornfield commentedon Sep 20, 2021
I think the probably has to do with implied nullability of the parquet schema (we would need to infer and remove nullness I think).
judahrand commentedon Sep 20, 2021
This works as a workaround:
I promise it does for realz this time.
emkornfield commentedon Sep 21, 2021
@judahrand so the fix is using
use_compliant_nested_type=True?judahrand commentedon Sep 21, 2021
use_compliant_nested_type=TrueANDparquet_options.enable_list_inference = True.Both are needed.
bnaul commentedon Sep 21, 2021
This is great, thanks @judahrand! Since as you point out the
list_inferenceparam is already exposed, I guess all that's needed to close this ticket is addinguse_compliant_nested_type=Trueinpython-bigquery/google/cloud/bigquery/_pandas_helpers.py
Lines 521 to 549 in ba02f24
judahrand commentedon Sep 21, 2021
Yeah, exposing it somewhere would be the best - maybe don't change the default just document it 😛
Only took 2+ years for an answer 🤣
judahrand commentedon Sep 21, 2021
I'm happy to look into this if no one else fancies.
fix: support ARRAY data type when loading from DataFrame with Parquet (…
fix: support ARRAY data type when loading from DataFrame with Parquet (…