Repository: pandas-dev/pandas (python)

Issue:
STYLE enable pylint's redefined-outer-name
This warning could potentially prevent bugs, so I'm pretty keen on putting in the effort to enable it.

Many people can work on this one together. To work on it, please:
1. choose 2-3 files
2. make sure you have `pylint` version 2.15.5 installed (`pip install pylint==2.15.5`) - check this is the same as the number in the `.pre-commit-config.yaml` file
3. for each file, run `pylint --disable=all --enable=redefined-outer-name <file>`
4. fix up the file until it passes
5. stage, commit, push, open pull request

For example, suppose you choose the file `pandas/io/json/_table_schema.py`. Running `pylint` on it, you get:
```
$ pylint --disable=all --enable=redefined-outer-name pandas/io/json/_table_schema.py
************* Module pandas.io.json._table_schema
pandas/io/json/_table_schema.py:314:23: W0621: Redefining name 'json' from outer scope (line 15) (redefined-outer-name)

-----------------------------------
Your code has been rated at 9.92/10
```

If we look at that file, we see that there's a function
```python
def parse_table_schema(json, precise_float):
```
which takes `json` as an argument, but the file also has
```python
from pandas._libs import json
```
at the top.

The simplest fix is probably to just modify the import to be
```python
from pandas._libs.json import loads
```
and then to remove

https://github.com/pandas-dev/pandas/blob/f9ff3796329e4bedb4a5477739f5eb8d2e40761d/pandas/io/json/_table_schema.py#L44

as `loads` is the only function used from `pandas._libs.json`.

---

Other notes:
1. multiple people can work on this issue at the same time, please don't comment 'take'
2. please choose no more than 3 files to work on in the same PR. Comment which files you'd like to take so we don't duplicate work

It's probably worth running `pylint` on a directory first to see what files need working on, e.g.
```
pylint --disable=all --enable=redefined-outer-name pandas/io/
```


---

Files that still need fixing:

- pandas/_version.py
- pandas/conftest.py
- pandas/core/generic.py
- pandas/core/internals/concat.py
- pandas/core/reshape/merge.py
- pandas/core/tools/datetimes.py
- pandas/io/formats/format.py
- pandas/io/formats/style.py
- pandas/io/json/_json.py
- pandas/io/xml.py
- pandas/util/_decorators.py
- pandas/util/_doctools.py

Let's ignore test files for now

Pull request:
gh-49656:Removing unwanted line in `.pre-commit-config.yaml`
- [x] closes #49656
- [ ] [Tests added and passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#writing-tests) if fixing a bug or adding a new feature
- [ ] All [code checks passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#pre-commit).
- [ ] Added [type annotations](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#type-hints) to new arguments/methods/functions.
- [ ] Added an entry in the latest `doc/source/whatsnew/vX.X.X.rst` file if fixing a bug or adding a new feature.


Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 82d8fc12c5..6a840ebfb9 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -95,7 +95,6 @@ repos:
             |^pandas/util/_test_decorators\.py  # keep excluded
             |^pandas/_version\.py  # keep excluded
             |^pandas/conftest\.py  # keep excluded
-            |^pandas/core/generic\.py
         args: [--disable=all, --enable=redefined-outer-name]
         stages: [manual]
 -   repo: https://github.com/PyCQA/isort
diff --git a/doc/source/reference/arrays.rst b/doc/source/reference/arrays.rst
index 5b41de4e12..aeaca7caea 100644
--- a/doc/source/reference/arrays.rst
+++ b/doc/source/reference/arrays.rst
@@ -60,6 +60,37 @@ is an :class:`ArrowDtype`.
 `Pyarrow <https://arrow.apache.org/docs/python/index.html>`__ provides similar array and `data type <https://arrow.apache.org/docs/python/api/datatypes.html>`__
 support as NumPy including first-class nullability support for all data types, immutability and more.
 
+The table below shows the equivalent pyarrow-backed (``pa``), pandas extension, and numpy (``np``) types that are recognized by pandas.
+Pyarrow-backed types below need to be passed into :class:`ArrowDtype` to be recognized by pandas e.g. ``pd.ArrowDtype(pa.bool_())``
+
+=============================================== ========================== ===================
+PyArrow type                                    pandas extension type      NumPy type
+=============================================== ========================== ===================
+:external+pyarrow:py:func:`pyarrow.bool_`       :class:`BooleanDtype`      ``np.bool_``
+:external+pyarrow:py:func:`pyarrow.int8`        :class:`Int8Dtype`         ``np.int8``
+:external+pyarrow:py:func:`pyarrow.int16`       :class:`Int16Dtype`        ``np.int16``
+:external+pyarrow:py:func:`pyarrow.int32`       :class:`Int32Dtype`        ``np.int32``
+:external+pyarrow:py:func:`pyarrow.int64`       :class:`Int64Dtype`        ``np.int64``
+:external+pyarrow:py:func:`pyarrow.uint8`       :class:`UInt8Dtype`        ``np.uint8``
+:external+pyarrow:py:func:`pyarrow.uint16`      :class:`UInt16Dtype`       ``np.uint16``
+:external+pyarrow:py:func:`pyarrow.uint32`      :class:`UInt32Dtype`       ``np.uint32``
+:external+pyarrow:py:func:`pyarrow.uint64`      :class:`UInt64Dtype`       ``np.uint64``
+:external+pyarrow:py:func:`pyarrow.float32`     :class:`Float32Dtype`      ``np.float32``
+:external+pyarrow:py:func:`pyarrow.float64`     :class:`Float64Dtype`      ``np.float64``
+:external+pyarrow:py:func:`pyarrow.time32`      (none)                     (none)
+:external+pyarrow:py:func:`pyarrow.time64`      (none)                     (none)
+:external+pyarrow:py:func:`pyarrow.timestamp`   :class:`DatetimeTZDtype`   ``np.datetime64``
+:external+pyarrow:py:func:`pyarrow.date32`      (none)                     (none)
+:external+pyarrow:py:func:`pyarrow.date64`      (none)                     (none)
+:external+pyarrow:py:func:`pyarrow.duration`    (none)                     ``np.timedelta64``
+:external+pyarrow:py:func:`pyarrow.binary`      (none)                     (none)
+:external+pyarrow:py:func:`pyarrow.string`      :class:`StringDtype`       ``np.str_``
+:external+pyarrow:py:func:`pyarrow.decimal128`  (none)                     (none)
+:external+pyarrow:py:func:`pyarrow.list_`       (none)                     (none)
+:external+pyarrow:py:func:`pyarrow.map_`        (none)                     (none)
+:external+pyarrow:py:func:`pyarrow.dictionary`  :class:`CategoricalDtype`  (none)
+=============================================== ========================== ===================
+
 .. note::
 
     For string types (``pyarrow.string()``, ``string[pyarrow]``), PyArrow support is still facilitated
diff --git a/doc/source/whatsnew/v2.0.0.rst b/doc/source/whatsnew/v2.0.0.rst
index 539e814f62..abaef639d6 100644
--- a/doc/source/whatsnew/v2.0.0.rst
+++ b/doc/source/whatsnew/v2.0.0.rst
@@ -881,6 +881,7 @@ Indexing
 - Bug in :func:`~DataFrame.describe` when formatting percentiles in the resulting index showed more decimals than needed (:issue:`46362`)
 - Bug in :meth:`DataFrame.compare` does not recognize differences when comparing ``NA`` with value in nullable dtypes (:issue:`48939`)
 - Bug in :meth:`DataFrame.isetitem` coercing extension array dtypes in :class:`DataFrame` to object (:issue:`49922`)
+- Bug in :class:`BusinessHour` would cause creation of :class:`DatetimeIndex` to fail when no opening hour was included in the index (:issue:`49835`)
 -
 
 Missing
@@ -951,6 +952,7 @@ Groupby/resample/rolling
 - Bug in :meth:`.SeriesGroupBy.nth` would raise when grouper contained NA values after subsetting from a :class:`DataFrameGroupBy` (:issue:`26454`)
 - Bug in :meth:`DataFrame.groupby` would not include a :class:`.Grouper` specified by ``key`` in the result when ``as_index=False`` (:issue:`50413`)
 - Bug in :meth:`.DataFrameGrouBy.value_counts` would raise when used with a :class:`.TimeGrouper` (:issue:`50486`)
+- Bug in :meth:`Resampler.size` caused a wide :class:`DataFrame` to be returned instead of a :class:`Series` with :class:`MultiIndex` (:issue:`46826`)
 -
 
 Reshaping
diff --git a/pandas/_libs/tslibs/offsets.pyx b/pandas/_libs/tslibs/offsets.pyx
index 0bc9751694..b59d17321d 100644
--- a/pandas/_libs/tslibs/offsets.pyx
+++ b/pandas/_libs/tslibs/offsets.pyx
@@ -1847,15 +1847,20 @@ cdef class BusinessHour(BusinessMixin):
         earliest_start = self.start[0]
         latest_start = self.start[-1]
 
+        if self.n == 0:
+            is_same_sign = sign > 0
+        else:
+            is_same_sign = self.n * sign >= 0
+
         if not self.next_bday.is_on_offset(other):
             # today is not business day
             other = other + sign * self.next_bday
-            if self.n * sign >= 0:
+            if is_same_sign:
                 hour, minute = earliest_start.hour, earliest_start.minute
             else:
                 hour, minute = latest_start.hour, latest_start.minute
         else:
-            if self.n * sign >= 0:
+            if is_same_sign:
                 if latest_start < other.time():
                     # current time is after latest starting time in today
                     other = other + sign * self.next_bday
diff --git a/pandas/core/resample.py b/pandas/core/resample.py
index 4af4be20a0..d6cba82476 100644
--- a/pandas/core/resample.py
+++ b/pandas/core/resample.py
@@ -989,6 +989,12 @@ class Resampler(BaseGroupBy, PandasObject):
     @doc(GroupBy.size)
     def size(self):
         result = self._downsample("size")
+
+        # If the result is a non-empty DataFrame we stack to get a Series
+        # GH 46826
+        if isinstance(result, ABCDataFrame) and not result.empty:
+            result = result.stack()
+
         if not len(self.ax):
             from pandas import Series
 


Test patch:
diff --git a/pandas/tests/resample/test_resampler_grouper.py b/pandas/tests/resample/test_resampler_grouper.py
index 0432cf3970..a521e24aa6 100644
--- a/pandas/tests/resample/test_resampler_grouper.py
+++ b/pandas/tests/resample/test_resampler_grouper.py
@@ -515,3 +515,25 @@ def test_resample_empty_Dataframe(keys):
         expected.index.name = keys[0]
 
     tm.assert_frame_equal(result, expected)
+
+
+def test_groupby_resample_size_all_index_same():
+    # GH 46826
+    df = DataFrame(
+        {"A": [1] * 3 + [2] * 3 + [1] * 3 + [2] * 3, "B": np.arange(12)},
+        index=date_range("31/12/2000 18:00", freq="H", periods=12),
+    )
+    result = df.groupby("A").resample("D").size()
+    expected = Series(
+        3,
+        index=pd.MultiIndex.from_tuples(
+            [
+                (1, Timestamp("2000-12-31")),
+                (1, Timestamp("2001-01-01")),
+                (2, Timestamp("2000-12-31")),
+                (2, Timestamp("2001-01-01")),
+            ],
+            names=["A", None],
+        ),
+    )
+    tm.assert_series_equal(result, expected)
diff --git a/pandas/tests/tseries/offsets/test_business_hour.py b/pandas/tests/tseries/offsets/test_business_hour.py
index 79e7a5ff67..319cc053d5 100644
--- a/pandas/tests/tseries/offsets/test_business_hour.py
+++ b/pandas/tests/tseries/offsets/test_business_hour.py
@@ -241,6 +241,12 @@ class TestBusinessHour:
 
         assert dt - offset2 == dt + _offset(-3)
 
+    def test_multiply_by_zero(self, dt, offset1, offset2):
+        assert dt - 0 * offset1 == dt
+        assert dt + 0 * offset1 == dt
+        assert dt - 0 * offset2 == dt
+        assert dt + 0 * offset2 == dt
+
     def testRollback1(
         self,
         dt,
@@ -972,6 +978,12 @@ class TestBusinessHour:
         for idx in [idx1, idx2, idx3]:
             tm.assert_index_equal(idx, expected)
 
+    def test_short_datetimeindex_creation(self):
+        # gh-49835
+        idx4 = date_range(start="2014-07-01 10:00", freq="BH", periods=1)
+        expected4 = DatetimeIndex(["2014-07-01 10:00"], freq="BH")
+        tm.assert_index_equal(idx4, expected4)
+
     def test_bday_ignores_timedeltas(self):
         idx = date_range("2010/02/01", "2010/02/10", freq="12H")
         t1 = idx + BDay(offset=Timedelta(3, unit="H"))