-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Scatter color: moving #10809 forward #12422
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
Changes from all commits
408f55d
6fa8249
0eed25a
27d829a
921100a
3f664a9
8c0a24e
6105980
c932a9e
85b1f96
e959692
f979b1f
a23a30b
09ff3df
ce98e8f
a2cec14
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
PathCollections created with `~.Axes.scatter` now keep track of invalid points | ||
`````````````````````````````````````````````````````````````````````````````` | ||
|
||
Previously, points with nonfinite (infinite or nan) coordinates would not be | ||
included in the offsets (as returned by `PathCollection.get_offsets`) of a | ||
`PathCollection` created by `~.Axes.scatter`, and points with nonfinite values | ||
(as specified by the *c* kwarg) would not be included in the array (as returned | ||
by `PathCollection.get_array`) | ||
|
||
Such points are now included, but masked out by returning a masked array. | ||
|
||
If the *plotnonfinite* kwarg to `~.Axes.scatter` is set, then points with | ||
nonfinite values are plotted using the bad color of the `PathCollection`\ 's | ||
colormap (as set by `Colormap.set_bad`). |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -174,7 +174,10 @@ def get_compressed_copy(self, mask): | |
def convert_to(self, unit): | ||
if unit == self.unit or not unit: | ||
return self | ||
new_value = self.unit.convert_value_to(self.value, unit) | ||
try: | ||
new_value = self.unit.convert_value_to(self.value, unit) | ||
except AttributeError: | ||
new_value = self | ||
return TaggedValue(new_value, unit) | ||
|
||
def get_value(self): | ||
|
@@ -345,7 +348,20 @@ def convert(val, unit, axis): | |
if units.ConversionInterface.is_numlike(val): | ||
return val | ||
if np.iterable(val): | ||
return [thisval.convert_to(unit).get_value() for thisval in val] | ||
if isinstance(val, np.ma.MaskedArray): | ||
val = val.astype(float).filled(np.nan) | ||
out = np.empty(len(val)) | ||
for i, thisval in enumerate(val): | ||
if np.ma.is_masked(thisval): | ||
out[i] = np.nan | ||
else: | ||
try: | ||
out[i] = thisval.convert_to(unit).get_value() | ||
except AttributeError: | ||
out[i] = thisval | ||
return out | ||
if np.ma.is_masked(val): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not equivalent:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But there is a check for np.iterable() in the block just above with early return, so here val is not an interable but a scalar. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's the point. In my example above I am using indexing to extract the masked scalar, which canot usefully be compared to np.ma.masked, but must instead be tested using is_masked. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, I see, it's that one just cannot compare with np.ma.masked using equality (because it behaves a bit like nan); one has to use is_masked similarly to isnan. |
||
return np.nan | ||
else: | ||
return val.convert_to(unit).get_value() | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1081,6 +1081,66 @@ def delete_masked_points(*args): | |
return margs | ||
|
||
|
||
def _combine_masks(*args): | ||
""" | ||
Find all masked and/or non-finite points in a set of arguments, | ||
and return the arguments as masked arrays with a common mask. | ||
|
||
Arguments can be in any of 5 categories: | ||
|
||
1) 1-D masked arrays | ||
2) 1-D ndarrays | ||
3) ndarrays with more than one dimension | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please indicate that the intent is to support RGB triplet arrays and that we don't expect invalid values there (as discussed in the call), to explain the behavior. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
4) other non-string iterables | ||
5) anything else | ||
|
||
The first argument must be in one of the first four categories; | ||
any argument with a length differing from that of the first | ||
argument (and hence anything in category 5) then will be | ||
passed through unchanged. | ||
|
||
Masks are obtained from all arguments of the correct length | ||
in categories 1, 2, and 4; a point is bad if masked in a masked | ||
array or if it is a nan or inf. No attempt is made to | ||
extract a mask from categories 2 and 4 if :meth:`np.isfinite` | ||
does not yield a Boolean array. Category 3 is included to | ||
support RGB or RGBA ndarrays, which are assumed to have only | ||
valid values and which are passed through unchanged. | ||
|
||
All input arguments that are not passed unchanged are returned | ||
as masked arrays if any masked points are found, otherwise as | ||
ndarrays. | ||
|
||
""" | ||
if not len(args): | ||
return () | ||
if is_scalar_or_string(args[0]): | ||
raise ValueError("First argument must be a sequence") | ||
nrecs = len(args[0]) | ||
margs = [] # Output args; some may be modified. | ||
seqlist = [False] * len(args) # Flags: True if output will be masked. | ||
masks = [] # List of masks. | ||
for i, x in enumerate(args): | ||
if is_scalar_or_string(x) or len(x) != nrecs: | ||
margs.append(x) # Leave it unmodified. | ||
else: | ||
if isinstance(x, np.ma.MaskedArray) and x.ndim > 1: | ||
raise ValueError("Masked arrays must be 1-D") | ||
x = np.asanyarray(x) | ||
if x.ndim == 1: | ||
x = safe_masked_invalid(x) | ||
seqlist[i] = True | ||
anntzer marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if np.ma.is_masked(x): | ||
masks.append(np.ma.getmaskarray(x)) | ||
margs.append(x) # Possibly modified. | ||
if len(masks): | ||
mask = np.logical_or.reduce(masks) | ||
for i, x in enumerate(margs): | ||
if seqlist[i]: | ||
margs[i] = np.ma.array(x, mask=mask) | ||
return margs | ||
|
||
|
||
def boxplot_stats(X, whis=1.5, bootstrap=None, labels=None, | ||
autorange=False): | ||
""" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can values still be masked even though you did
val = val.(etc).filled(np.nan)
if val is a masked array just above?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The point here is that convert needs to be able to work on a masked scalar if that is what was provided as the input argument. If it gets a masked scalar with mask True it needs to replace it with np.nan.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This remains not totally clear to me, but in any case it's at worse a dead branch (and much more likely it's just me not understanding masked arrays well enough), so I'm not going to hold up the PR on that.