Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use new NA scalar in BooleanArray #29961

Merged

Conversation

jorisvandenbossche
Copy link
Member

Follow-up on #29597 and #29555, now actually using the pd.NA scalar in BooleanArray.

@jorisvandenbossche jorisvandenbossche added ExtensionArray Extending pandas with custom dtypes or arrays. Missing-data np.nan, pd.NaT, pd.NA, dropna, isnull, interpolate labels Dec 2, 2019
@jorisvandenbossche jorisvandenbossche added this to the 1.0 milestone Dec 2, 2019
Copy link
Contributor

@TomAugspurger TomAugspurger left a comment

Choose a reason for hiding this comment

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

LGTM at a glance. Request for a few more tests / confirmation that these are already testsed:

  1. Test to enure that array([True, False, None, np.nan pd.NA], dtype="boolean") correctly sanitizes all the NA-like value to be NA.
  2. Test in BooleanArray.__setitem__ ensuring that arr[0] = np.nan, etc., always inserts NA.

pandas/core/arrays/boolean.py Show resolved Hide resolved
@jorisvandenbossche
Copy link
Member Author

One question that comes up here (but the same question applies to string and integer arrays): what "NA value" do we want to use when converting to object dtype numpy arrays? None or pd.NA ?

In the initial BooleanArray PR, I used None (since pd.NA was not there yet), so you get a numpy array like np.array([True, False, None]).
Now we can start using pd.NA, which is closer to the pandas representation (and what you would get from iteration or conversion to list np.array([i for i in arr])). But on the other hand, None can be easier to handle for cases where you need a numpy array (functionality that needs numpy arrays will typically also not recognize or handle correctly pd.NA).

@TomAugspurger
Copy link
Contributor

I'm not sure, but my initial preference is for having pd.NA, with an option to get other values for NA upon request. I think that means we should have a somewhat standard to_numpy method

def to_numpy(self, dtype=object, na_value=pd.NA):
    ... 

Then if the user wants None / NaN, they can request it relatively easily.

do we want to use when converting to object dtype numpy arrays?

What are the user-actions that hit this?

  1. np.array(boolarray, dtype=...)
  2. boolarray.astype(np_dtype)
  3. ...?

@jorisvandenbossche
Copy link
Member Author

Examples where the pd.NA in a numpy array gives problems: #29976 (pyarrow's C conversion code does not know it) and the factorize errors in #29964 (our cython hashing code for object arrays cannot handle it).

Now, both are easy to solve (certainly since both can easily be solved on our side; the hashing code can recognize pd.NA and for the conversion to pyarrow we can ensure to use None before passing to pyarrow). But it are examples of how other code can break.

Still not sure that we should therefore use None as the default in __array__, but at least I think it is important to have this to_numpy(.., na_value=..).

Copy link
Contributor

@TomAugspurger TomAugspurger left a comment

Choose a reason for hiding this comment

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

For the "what value to use when converting", I'm not sure that downstream projects not understanding pd.NA should drive our decision here. We'll be living with this decision for a while, and projects will have time to adapt.

@@ -281,7 +281,9 @@ def __getitem__(self, item):
return self._data[item]
return type(self)(self._data[item], self._mask[item])

def _coerce_to_ndarray(self, force_bool: bool = False):
def _coerce_to_ndarray(
self, force_bool: bool = False, na_value: "Scalar" = lib._no_default
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason to prefer lib_no_default to just libmissing.NA directly?

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm, not sure. I was probably sidetracked by the idea I could not use None as default as that is a valid value as well ..
(if we want to have this generic / shared with other arrays, using self._na_value might be useful, but I don't think we will share this with arrays that don't use pd.NA, so ..)

@jorisvandenbossche
Copy link
Member Author

@TomAugspurger updated this

@TomAugspurger
Copy link
Contributor

See #30043 for the CI failures. OK to ignore for now.

Copy link
Contributor

@TomAugspurger TomAugspurger left a comment

Choose a reason for hiding this comment

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

I think we can move forward with this, despite the ongoing API discussion about .to_numpy / .astype(float) / np.asarray(arr), since that will be affecting all IntegerArray / StringArray / BooleanArray.

if is_integer_dtype(dtype):
if self.isna().any():
raise ValueError("cannot convert NA to integer")
# for float dtype, ensure we use np.nan before casting (numpy cannot
Copy link
Contributor

Choose a reason for hiding this comment

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

Pending the discussion in #30038.

@TomAugspurger
Copy link
Contributor

One more note: we'll need to handle reductions like any and all. They look somewhat buggy on master though.

In [16]: pd.array([True, None])._reduce('all')
Out[16]: True

In [17]: pd.array([True, None])._reduce('all', skipna=False)
Out[17]: True

In [18]: pd.array([False, None])._reduce('all', )
Out[18]: False

In [19]: pd.array([False, None])._reduce('all', skipna=False)
Out[19]: False

Do we want to do that here or as a followup?

@jorisvandenbossche
Copy link
Member Author

Let's do that as a follow-up, that's a remainder from the initial implementation, it's noted as a to do item in #29556

@TomAugspurger
Copy link
Contributor

Sounds good to me.

@jorisvandenbossche
Copy link
Member Author

OK, going to merge this then, so the Kleene PR can then be updated.

@jorisvandenbossche jorisvandenbossche merged commit e73ed45 into pandas-dev:master Dec 4, 2019
@jorisvandenbossche jorisvandenbossche deleted the boolean-use-NA branch December 4, 2019 19:27
@jorisvandenbossche
Copy link
Member Author

Once the IntegerArray PR is in, I will also take a look at consolidating both classes

@TomAugspurger
Copy link
Contributor

I'll update the Kleene PR now, and the Integer one after if I have a chance.

@jorisvandenbossche
Copy link
Member Author

I did a quick PR for the any/all: #30062

proost pushed a commit to proost/pandas that referenced this pull request Dec 19, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ExtensionArray Extending pandas with custom dtypes or arrays. Missing-data np.nan, pd.NaT, pd.NA, dropna, isnull, interpolate
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants