Repository: hgrecco/pint-pandas

Issue:
Error applying assignment operators to series
@hgrecco thanks very much for the recent 0.2 release! I was already making use of the new #47 by installing the dev build, but am happy to see it released 🙂 

I came across some edge cases when using assignment operators and compound assignment operators (e.g., `+=`); particularly with _empty_ slices of a series (see the following code).

```python
>>> import pandas as pd
>>> import pint
>>> import pint_pandas
>>> import pytest
>>> 
>>> 
>>> ureg = pint.get_application_registry()
>>> 
>>> pint_pandas.show_versions()
{'numpy': '1.20.1', 'pandas': '1.1.5', 'pint': '0.17', 'pint_pandas': '0.2'}
>>> durations = pd.Series(1.0, index=range(5))
>>> durations.loc[:] += 1  # OK
>>> durations.loc[[]] += 1  # OK
>>> durations.loc[:] = 1  # OK
>>> durations.loc[[]] = 1  # OK
>>> 
>>> durations = pd.Series(1.0, index=range(5), dtype="pint[meter]")
>>> 
>>> durations[:] += 1 * ureg.meter  # OK
>>> with pytest.raises(IndexError):
...     durations[[]] += 1 * ureg.meter  # Fails: `IndexError: list index out of range`
>>> with pytest.raises(TypeError):
...     durations[:] = 1 * ureg.meter  # Fails: `TypeError: object of type 'int' has no len()`
>>> with pytest.raises(TypeError):
...     durations[[]] = 1 * ureg.meter  # Fails: `TypeError: object of type 'int' has no len()`
```

As shown in the example above, the `=` operator and the `+=` operator both work on a series with `dtype=float`, whether empty or not. However, when using a pint array series, the operator fails under the following conditions:

| Operator | Series | Result |
| -- | -- | -- |
| `=` | Non-empty | ❌ `TypeError` |
| `=` | Empty | ❌ `TypeError` |
| `+=` | Non-empty | ✔️ OK |
| `+=` | Empty | ❌ `IndexError` |

I was able to work around the issue in my case by checking the size of the slice before affected assignment statements, skipping the assignment as needed.

Discussion:

Patch:
diff --git a/CHANGES b/CHANGES
index 525d271..6ca212e 100644
--- a/CHANGES
+++ b/CHANGES
@@ -11,6 +11,7 @@ pint-pandas Changelog
 - Fixed a bug where units of a quantity where ignored when creating a PintArray. #104
 - Quantity arrays may be used to initialise PintArrays without specifying the dtype. #104
 - Fixed a bug where `.astype("string[pyarrow]")` would not create pyarrow-backed arrays. #101
+- Fixed bug preventing adding to empty slices of PintArrays #69
 
 0.2 (2021-03-23)
 ----------------
diff --git a/pint_pandas/pint_array.py b/pint_pandas/pint_array.py
index a6b8536..0ea67c2 100644
--- a/pint_pandas/pint_array.py
+++ b/pint_pandas/pint_array.py
@@ -283,7 +283,7 @@ class PintArray(ExtensionArray, ExtensionOpsMixin):
 
         if isinstance(value, _Quantity):
             value = value.to(self.units).magnitude
-        elif is_list_like(value) and isinstance(value[0], _Quantity):
+        elif is_list_like(value) and len(value) > 0 and isinstance(value[0], _Quantity):
             value = [item.to(self.units).magnitude for item in value]
 
         key = check_array_indexer(self, key)
@@ -626,7 +626,11 @@ class PintArray(ExtensionArray, ExtensionOpsMixin):
                     return param.quantity
                 elif isinstance(param, (_Quantity, _Unit)):
                     return param
-                elif is_list_like(param) and isinstance(param[0], _Quantity):
+                elif (
+                    is_list_like(param)
+                    and len(param) > 0
+                    and isinstance(param[0], _Quantity)
+                ):
                     units = param[0].units
                     return type(param[0])([p.m_as(units) for p in param], units)
                 else:
@@ -731,7 +735,7 @@ class PintArray(ExtensionArray, ExtensionOpsMixin):
         arr = self._data
         if isinstance(value, _Quantity):
             value = value.to(self.units).magnitude
-        elif is_list_like(value) and isinstance(value[0], _Quantity):
+        elif is_list_like(value) and len(value) > 0 and isinstance(value[0], _Quantity):
             value = [item.to(self.units).magnitude for item in value]
         return arr.searchsorted(value, side=side, sorter=sorter)
 


Test patch:
diff --git a/pint_pandas/testsuite/test_pandas_interface.py b/pint_pandas/testsuite/test_pandas_interface.py
index ddd4be0..6f71263 100644
--- a/pint_pandas/testsuite/test_pandas_interface.py
+++ b/pint_pandas/testsuite/test_pandas_interface.py
@@ -516,6 +516,13 @@ class TestArithmeticOps(base.BaseArithmeticOpsTests):
         else:
             raise pytest.skip(f"{type(data).__name__} does not implement add")
 
+    def test_assignment_add_empty(self, data):
+        # GH 68
+        result = pd.Series(data)
+        result[[]] += data[0]
+        expected = pd.Series(data)
+        self.assert_series_equal(result, expected)
+
 
 class TestComparisonOps(base.BaseComparisonOpsTests):
     def _compare_other(self, s, data, op_name, other):