Skip to content
This repository was archived by the owner on May 7, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b62f1ca
chore: clean up OWNERS
Aug 5, 2024
ef7eaf8
Merge branch 'googleapis:main' into main
mattyopl Aug 6, 2024
cd97a45
feat: support `Index.to_frame()`
Aug 8, 2024
860f370
Merge branch 'googleapis:main' into main
mattyopl Aug 8, 2024
8761d8a
feat: support `Index.to_frame()`
Aug 8, 2024
c68a5f5
Merge branch 'main' of github.com:mattyopl/python-bigquery-dataframes
Aug 8, 2024
7c2e1b0
Merge branch 'main' of github.com:mattyopl/python-bigquery-dataframes
Aug 8, 2024
e9bffa8
Merge branch 'main' of github.com:mattyopl/python-bigquery-dataframes
Aug 8, 2024
0f2e291
Merge branch 'main' into main
mattyopl Aug 8, 2024
2e1afff
Merge branch 'main' of github.com:mattyopl/python-bigquery-dataframes
Aug 8, 2024
e376b44
Merge branch 'main' of github.com:mattyopl/python-bigquery-dataframes
Aug 9, 2024
31f3db8
Merge branch 'main' of github.com:mattyopl/python-bigquery-dataframes
Aug 9, 2024
e813c61
Merge branch 'main' of github.com:mattyopl/python-bigquery-dataframes
Aug 9, 2024
32eb06c
Merge branch 'main' of github.com:mattyopl/python-bigquery-dataframes
Aug 9, 2024
60bea0a
Merge branch 'main' of github.com:mattyopl/python-bigquery-dataframes
Aug 9, 2024
ec97d72
Merge branch 'main' into main
mattyopl Aug 9, 2024
af9f7a4
Merge branch 'main' of github.com:mattyopl/python-bigquery-dataframes
Aug 9, 2024
b212126
Merge branch 'main' of github.com:mattyopl/python-bigquery-dataframes
Aug 9, 2024
9c31fb3
Merge branch 'main' of github.com:mattyopl/python-bigquery-dataframes
Aug 9, 2024
266feae
Merge branch 'main' of github.com:mattyopl/python-bigquery-dataframes
Aug 12, 2024
43f826c
Merge branch 'main' of github.com:mattyopl/python-bigquery-dataframes
Aug 12, 2024
5d388da
Merge branch 'main' of github.com:mattyopl/python-bigquery-dataframes
Aug 12, 2024
c368362
Merge branch 'main' of github.com:mattyopl/python-bigquery-dataframes
Aug 12, 2024
3d2ba36
Merge branch 'main' of github.com:mattyopl/python-bigquery-dataframes
Aug 12, 2024
2966f08
Merge branch 'googleapis:main' into main
mattyopl Aug 14, 2024
ab2753e
Merge branch 'googleapis:main' into main
mattyopl Sep 9, 2024
777ff7d
Merge branch 'googleapis:main' into main
mattyopl Sep 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions bigframes/core/indexes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,19 @@ def from_frame(
index._linked_frame = frame
return index

def to_frame(
Comment thread
mattyopl marked this conversation as resolved.
self, index: bool = True, name: blocks.Label | None = None
) -> bigframes.dataframe.DataFrame:
provided_name = name if name else self.name
series = self.to_series()
series.name = provided_name
frame = series.to_frame()
if index: # matching pandas behavior
frame.index.name = self.name
else:
frame = frame.reset_index(drop=True)
return frame

@property
def _session(self):
return self._block.session
Expand Down
27 changes: 27 additions & 0 deletions bigframes/core/indexes/multi.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
import bigframes_vendored.pandas.core.indexes.multi as vendored_pandas_multindex
import pandas

import bigframes.core.blocks as blocks
from bigframes.core.indexes.base import Index
import bigframes.dataframe


class MultiIndex(Index, vendored_pandas_multindex.MultiIndex):
Expand All @@ -46,3 +48,28 @@ def from_arrays(
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))

def to_frame(
self,
index: bool = True,
name: Sequence[blocks.Label] | blocks.Label | None = None,
) -> bigframes.dataframe.DataFrame:
columns = [
[self.values[j][i] for j in range(len(self.values))]
for i in range(len(self.values[0]))
]
if isinstance(name, Sequence):
if len(name) != len(columns):
raise ValueError(
"Length of provided names must match length of MultiIndex columns"
)
data = {name[i]: column for i, column in enumerate(columns)}
elif name is None:
data = {i: column for i, column in enumerate(columns)}
else:
raise ValueError("'name' parameter must be of type Sequence")
original_index = columns
result = bigframes.dataframe.DataFrame(
data, index=original_index if index else None
)
return result
25 changes: 25 additions & 0 deletions tests/system/small/test_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import pandas as pd
import pytest

import bigframes.core.indexes as indexes
import bigframes.pandas as bpd
from tests.system.utils import assert_pandas_index_equal_ignore_index_type

Expand Down Expand Up @@ -320,6 +321,30 @@ def test_index_to_series(
pd.testing.assert_series_equal(bf_result, pd_result)


@pytest.mark.parametrize("index_arg", [True, False])
@pytest.mark.parametrize("name_arg", [None, "food"])
def test_index_to_frame(index_arg, name_arg):
pd_idx: pd.Index = pd.Index(
["Ant", "Bear", "Cow"], name="animal", dtype="string[pyarrow]"
)
bf_idx = indexes.Index(["Ant", "Bear", "Cow"], name="animal")

if name_arg is None:

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.

It doesn't need the if else condition. Just

pd_df = pd_idx.to_frame(index=index_arg, name=name_arg)
bf_df = bf_idx.to_frame(index=index_arg, name=name_arg)

is good enough

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.

Pandas implementation of name arg handling is slightly different. If we set name=None for pandas, it will create a DataFrame with column names as string None, since technically the default is lib.nodefault (code pointer: https://github.com/pandas-dev/pandas/blob/v2.2.2/pandas/_libs/lib.pyi#L32), whereas use None: https://github.com/pandas-dev/pandas/blob/v2.2.2/pandas/core/indexes/base.py#L1607-L1666

@mattyopl mattyopl Aug 12, 2024

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.

I can add something similar to BigFrames if you think it is better to exactly mirror pandas functionality: I am equally happy either way

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.

Ah, that's a weird behavior of pandas. Thanks for pointing out. Then we shouldn't let the default to be None. But should have sth similar. @TrevorBergeron

pd_df = pd_idx.to_frame(index=index_arg)
bf_df = bf_idx.to_frame(index=index_arg)
else:
pd_df = pd_idx.to_frame(index=index_arg, name=name_arg)
bf_df = bf_idx.to_frame(index=index_arg, name=name_arg)
pd.testing.assert_frame_equal(
pd_df, bf_df.to_pandas(), check_column_type=False, check_index_type=False
)
# BigFrames type casting is weird
# automatically casts dtype to string whereas pandas dtype is object
# additionally, pandas uses string[python] and BigFrames uses string[pyarrow]
# so we set dtype in pandas index creation
# similarly, pandas uses int64 dtype for numerical index and BigFrames uses Int64


@pytest.mark.parametrize(
("how",),
[
Expand Down
22 changes: 22 additions & 0 deletions tests/system/small/test_multiindex.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import pandas
import pytest

import bigframes.core.indexes as indexes
import bigframes.pandas as bpd
from tests.system.utils import assert_pandas_df_equal, skip_legacy_pandas

Expand Down Expand Up @@ -45,6 +46,27 @@ def test_multi_index_from_arrays():
pandas.testing.assert_index_equal(bf_idx.to_pandas(), pd_idx)


@pytest.mark.parametrize("index_arg", [True, False])
@pytest.mark.parametrize("name_arg", [None, ["x", "y"]])
def test_multi_index_to_frame(index_arg, name_arg):

pd_idx = pandas.MultiIndex.from_arrays([["a", "b", "c"], ["d", "e", "f"]])
bf_idx = indexes.MultiIndex.from_arrays([["a", "b", "c"], ["d", "e", "f"]])
if name_arg is None:

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.

same here.

pd_df = pd_idx.to_frame(index=index_arg)
bf_df = bf_idx.to_frame(index=index_arg)
else:
pd_df = pd_idx.to_frame(index=index_arg, name=name_arg)
bf_df = bf_idx.to_frame(index=index_arg, name=name_arg)
pandas.testing.assert_frame_equal(
pd_df,
bf_df.to_pandas(),
check_dtype=False,
check_column_type=False,
check_index_type=False,
)


@skip_legacy_pandas
def test_read_pandas_multi_index_axes():
index = pandas.MultiIndex.from_arrays(
Expand Down