Repository: matplotlib/matplotlib
Pull request: Fix collections coerce float
## PR Summary

Collections `set_offset` was coercing to float, which ruins any units we want to do.  Closes #21803

Needs tests....

Tested with 
```python
import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0, 10)
y = np.sin(x)
d = x * np.timedelta64(24, 'h') + np.datetime64('2021-11-29')

sc = plt.scatter(d, y) # showsw a plot
sc.set_offsets(list(zip(d, y)))
plt.show()
```



## PR Checklist

<!-- Please mark any checkboxes that do not apply to this PR as [N/A]. -->
**Tests and Styling**
- [ ] Has pytest style unit tests (and `pytest` passes).
- [ ] Is [Flake 8](https://flake8.pycqa.org/en/latest/) compliant (install `flake8-docstrings` and run `flake8 --docstring-convention=all`).

**Documentation**
- [ ] New features are documented, with examples if plot related.
- [ ] New features have an entry in `doc/users/next_whats_new/` (follow instructions in README.rst there).
- [ ] API changes documented in `doc/api/next_api_changes/` (follow instructions in README.rst there).
- [ ] Documentation is sphinx and numpydoc compliant (the docs should [build](https://matplotlib.org/devel/documenting_mpl.html#building-the-docs) without error).

<!--
Thank you so much for your PR!  To help us review your contribution, please
consider the following points:

- A development guide is available at https://matplotlib.org/devdocs/devel/index.html.

- Help with git and github is available at
  https://matplotlib.org/devel/gitwash/development_workflow.html.

- Do not create the PR out of main, but out of a separate branch.

- The PR title should summarize the changes, for example "Raise ValueError on
  non-numeric input to set_xlim".  Avoid non-descriptive titles such as
  "Addresses issue #8576".

- The summary should provide at least 1-2 sentences describing the pull request
  in detail (Why is this change required?  What problem does it solve?) and
  link to any relevant issues.

- If you are contributing fixes to docstrings, please pay attention to
  http://matplotlib.org/devel/documenting_mpl.html#formatting.  In particular,
  note the difference between using single backquotes, double backquotes, and
  asterisks in the markup.

We understand that PRs can sometimes be overwhelming, especially as the
reviews start coming in.  Please let us know if the reviews are unclear or
the recommended next step seems overly demanding, if you would like help in
addressing a reviewer's comments, or if you have been waiting too long to hear
back on your PR.
-->


Problem statement:
[Bug]: using `set_offsets` on scatter object raises TypeError
### Bug summary

`plt.scatter` can accept datetime as arguments, but `set_offsets` method of it's returns cannot.

### Code for reproduction

```python
import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0, 10)
y = np.sin(x)
d = x * np.timedelta64(24, 'h') + np.datetime64('2021-11-29')

sc = plt.scatter(d, y) # shows a plot
sc.set_offsets(list(zip(d, y))) # clears the plot so it becomes just white square
```


### Actual outcome

The scatters just disappear if you use other `timedelta64` units than `D`, also if you use other date unit, than day.

### Expected outcome

Excepted outcome is to update positions of scatters

### Additional information

_No response_

### Operating system

Arch

### Matplotlib Version

3.5.0

### Matplotlib Backend

Qt5Agg, `'module://ipympl.backend_nbagg'`

### Python version

Python 3.9.6

### Jupyter version

3.1.7

### Installation

conda

Hints from issue discussion:
Thanks, but this has been fixed, at least as of 3.5, so I'll close.  
Well, I've just updated to 3.5.0 and the bug is still here with the exact same error.

UPD updating `numpy` to 1.21.4 helped
UPD2 it seems that it works for days, but not for hours. So if I replace `timedelta64(1, 'D')` with `timedelta64(24, 'h')` the code still does not work
Please update the issue with the non working code and the error message. Thanks!  
Done
Agreed - `set_offset` coerces to float, which of course gets rid of your datetime.  As a work around, you can convert to float before passing: `list(zip(mdates.date2num(d), y))` but this should also be fixed on our side.  Thanks!

Commit message: FIX: collections set_offset co-ercing to float

Patch to review:
diff --git a/lib/matplotlib/collections.py b/lib/matplotlib/collections.py
index 47f2c9503be7..a31afb86f0c6 100644
--- a/lib/matplotlib/collections.py
+++ b/lib/matplotlib/collections.py
@@ -545,9 +545,12 @@ def set_offsets(self, offsets):
         ----------
         offsets : (N, 2) or (2,) array-like
         """
-        offsets = np.asanyarray(offsets, float)
+        offsets = np.asanyarray(offsets)
         if offsets.shape == (2,):  # Broadcast (2,) -> (1, 2) but nothing else.
             offsets = offsets[None, :]
+        offsets[:, 0] = self.convert_xunits(offsets[:, 0])
+        offsets[:, 1] = self.convert_yunits(offsets[:, 1])
+        offsets = np.asarray(offsets, 'float')
         self._offsets = offsets
         self.stale = True
 
diff --git a/lib/matplotlib/tests/test_collections.py b/lib/matplotlib/tests/test_collections.py
index e528fdc86be3..db458e4b5c5f 100644
--- a/lib/matplotlib/tests/test_collections.py
+++ b/lib/matplotlib/tests/test_collections.py
@@ -1078,3 +1078,23 @@ def test_set_offset_transform():
     late.set_offset_transform(skew)
 
     assert skew == init.get_offset_transform() == late.get_offset_transform()
+
+
+def test_set_offset_units():
+    # passing the offsets in initially (i.e. via scatter)
+    # should yield the same results as `set_offsets`
+    x = np.linspace(0, 10, 5)
+    y = np.sin(x)
+    d = x * np.timedelta64(24, 'h') + np.datetime64('2021-11-29')
+
+    sc = plt.scatter(d, y)
+    off0 = sc.get_offsets()
+    sc.set_offsets(list(zip(d, y)))
+    np.testing.assert_allclose(off0, sc.get_offsets())
+
+    # try the other way around
+    fig, ax = plt.subplots()
+    sc = ax.scatter(y, d)
+    off0 = sc.get_offsets()
+    sc.set_offsets(list(zip(y, d)))
+    np.testing.assert_allclose(off0, sc.get_offsets())


Code review comments:
diff_hunk: @@ -545,9 +545,12 @@ def set_offsets(self, offsets):
         ----------
         offsets : (N, 2) or (2,) array-like
         """
-        offsets = np.asanyarray(offsets, float)
+        offsets = np.asanyarray(offsets)
         if offsets.shape == (2,):  # Broadcast (2,) -> (1, 2) but nothing else.
             offsets = offsets[None, :]
+        offsets[:, 0] = self.convert_xunits(offsets[:, 0])
+        offsets[:, 1] = self.convert_yunits(offsets[:, 1])
+        offsets = np.asarray(offsets, 'float')
path: lib/matplotlib/collections.py
text: @user1:
If we're going to re-cast, is there any reason to hang on to the original array? Can we do `np.column_stack((convertx(...), converty(...)))`?

@author:
sure, done.  Not sure it is any more elegant, but probably better to convert the x and y arrays separately to float anyway (they can be object arrays, even though convert_xunits has been called, which actually strikes me as a problem in convert_x/yunits.