Skip to content

feat(bigframes): support offset-based column access via iloc#17367

Open
tswast wants to merge 16 commits into
mainfrom
b519239774-json-formatting
Open

feat(bigframes): support offset-based column access via iloc#17367
tswast wants to merge 16 commits into
mainfrom
b519239774-json-formatting

Conversation

@tswast

@tswast tswast commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

🦕

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for iloc column-based assignment (__setitem__) in DataFrames and refactors iloc indexing to dynamically enforce ordering only when row subsets or specific orderings are requested. It also updates JSON column serialization in _get_display_df to use the new iloc assignment, bypassing limitations with duplicate or non-string column names. Feedback on these changes includes a recommendation to use _assign_multi_items instead of assign in __setitem__ to prevent potential TypeErrors with non-string column labels, removing a useless expression (df._block.apply_analytic) in _get_display_df, and reverting a hardcoded development project ID in the generative AI notebook to a placeholder.

Comment thread packages/bigframes/bigframes/core/indexers.py Outdated
Comment thread packages/bigframes/bigframes/dataframe.py Outdated
Comment thread packages/bigframes/notebooks/generative_ai/ai_functions.ipynb Outdated
@tswast tswast closed this Jul 1, 2026
@tswast tswast changed the title fix(bigframes): avoid exceptions for unnamed JSON columns in SQL Cell outputs feat(bigframes): support offset-based column access via iloc Jul 8, 2026
@tswast tswast reopened this Jul 8, 2026
@tswast tswast marked this pull request as ready for review July 8, 2026 18:44
@tswast tswast requested review from a team as code owners July 8, 2026 18:45
@tswast tswast requested review from TrevorBergeron and removed request for a team July 8, 2026 18:45
Comment on lines +107 to +113
if (
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
):
requires_ordering = False

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we extract this out to a helper? The idea being we want iloc[:] to work without ordering?

@tswast tswast Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, helper makes sense to me. Might be able to keep it from getting out of sync that way.

Yes, that's the idea. If they select all rows, we don't care about the ordering.

Comment on lines +2325 to +2327
_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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need these aliases?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not strictly necessary, but kinda nice to be able to be clear about the usage. Alternatively, I could remove _assign_multi_items and just rename it to _assign_multi_items_by_labels. What do you think?

col_indexer_list = list(col_indexer)

if len(col_indexer_list) > 0 and all(
isinstance(x, bool) for x in col_indexer_list

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might we need something more general than isintance to handle eg numpy bool?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also similar concern with other isinstance checks elsewhere

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I might be able to use the typing.abc classes instead. I'll check on this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in the latest commits. Added support for pyarrow scalars too.

Comment on lines +314 to +356
if isinstance(col_indexer, int):
col_offset = col_indexer
if col_offset < 0:
col_offset += n_cols
if col_offset < 0 or col_offset >= n_cols:
raise IndexError("single positional indexer is out-of-bounds")

df = self._dataframe._assign_multi_items_by_offsets([col_offset], value)
self._dataframe._set_block(df._get_block())

elif isinstance(col_indexer, slice):
col_offsets = list(range(*col_indexer.indices(n_cols)))
if not col_offsets:
return
df = self._dataframe._assign_multi_items_by_offsets(col_offsets, value)
self._dataframe._set_block(df._get_block())

elif pd.api.types.is_list_like(col_indexer):
col_indexer_list = list(col_indexer)

if len(col_indexer_list) > 0 and all(
isinstance(x, bool) for x in col_indexer_list
):
if len(col_indexer_list) != n_cols:
raise ValueError(
f"Boolean index has wrong length: {len(col_indexer_list)} instead of {n_cols}"
)
col_offsets = [i for i, val in enumerate(col_indexer_list) if val]
else:
col_offsets = []
for idx in col_indexer_list:
if isinstance(idx, bool):
raise TypeError("pos list must contain only integers")
if not isinstance(idx, int):
raise TypeError("pos list must contain only integers")
if idx < 0:
idx += n_cols
if idx < 0 or idx >= n_cols:
raise IndexError("positional indexer is out-of-bounds")
col_offsets.append(idx)

if not col_offsets:
return

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems all of this is about essentially converting a key to an offset list. and then doing the same _assign_multi_items_by_offsets and then _set_block sequence given that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good callout. I added _iloc_col_indexer_to_offsets and _iloc_clip_to_offset helper functions along these lines. I also realized that getitem is sharing quite a bit of logic here too and updated it to use the same helpers.


def _iloc_col_indexer_to_offsets(
df: bigframes.dataframe.DataFrame, col_indexer: Any
) -> Tuple[Sequence[int], str]:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably better to put the return type logic in the getitem implementation even if it means a little bit of duplicate code to determine if col_indexer is a scalar integer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in latest commit.

return False


def pyarrow_array_from_sequence(data: Any, pa_type: pa.DataType) -> pa.Array:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q: can we not just use the ManagedArrowTable.from_pyarrow to ingest this data? AFAICT we are using raw string datatype for json in our representation, is this not working to dodge the extension type issue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants