Repository: pandas-dev/pandas (python)

Issue:
ENH: Add `tzdata` to the `_hard_dependencies`
### Feature Type

- [ ] Adding new functionality to pandas

- [x] Changing existing functionality in pandas

- [ ] Removing existing functionality in pandas


### Problem Description

In https://github.com/pandas-dev/pandas/pull/61084#pullrequestreview-2669422152,  suggesting that `tzdata` should be added to the `_hard_dependencies` list.

### Feature Description

Extend current `_hard_dependencies` from `("numpy", "dateutil")` to `("numpy", "dateutil", "tzdata")`. 

### Alternative Solutions

No

### Additional Context

_No response_

Pull request:
ENH: Add tzdata to hard dependencies
- [x] closes #61273
- [x] [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
- [x] All [code checks passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#pre-commit).
- [x] Added [type annotations](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#type-hints) to new arguments/methods/functions.
- [x] Added an entry in the latest `doc/source/whatsnew/v3.0.0.rst` file if fixing a bug or adding a new feature.


Patch:
diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst
index 7c9d78acc0..f76d94036c 100644
--- a/doc/source/whatsnew/v3.0.0.rst
+++ b/doc/source/whatsnew/v3.0.0.rst
@@ -767,6 +767,7 @@ I/O
 - Bug in :meth:`DataFrame.to_dict` raises unnecessary ``UserWarning`` when columns are not unique and ``orient='tight'``. (:issue:`58281`)
 - Bug in :meth:`DataFrame.to_excel` when writing empty :class:`DataFrame` with :class:`MultiIndex` on both axes (:issue:`57696`)
 - Bug in :meth:`DataFrame.to_excel` where the :class:`MultiIndex` index with a period level was not a date (:issue:`60099`)
+- Bug in :meth:`DataFrame.to_stata` when exporting a column containing both long strings (Stata strL) and :class:`pd.NA` values (:issue:`23633`)
 - Bug in :meth:`DataFrame.to_stata` when writing :class:`DataFrame` and ``byteorder=`big```. (:issue:`58969`)
 - Bug in :meth:`DataFrame.to_stata` when writing more than 32,000 value labels. (:issue:`60107`)
 - Bug in :meth:`DataFrame.to_string` that raised ``StopIteration`` with nested DataFrames. (:issue:`16098`)
diff --git a/pandas/__init__.py b/pandas/__init__.py
index 5dc6a8c3bc..7d6dd7b7c1 100644
--- a/pandas/__init__.py
+++ b/pandas/__init__.py
@@ -3,7 +3,7 @@ from __future__ import annotations
 __docformat__ = "restructuredtext"
 
 # Let users know if they're missing any of our hard dependencies
-_hard_dependencies = ("numpy", "dateutil")
+_hard_dependencies = ("numpy", "dateutil", "tzdata")
 
 for _dependency in _hard_dependencies:
     try:
diff --git a/pandas/core/arrays/masked.py b/pandas/core/arrays/masked.py
index 62e6119204..e7a6b20736 100644
--- a/pandas/core/arrays/masked.py
+++ b/pandas/core/arrays/masked.py
@@ -515,7 +515,7 @@ class BaseMaskedArray(OpsMixin, ExtensionArray):
         if self.ndim > 1:
             return [x.tolist() for x in self]
         dtype = None if self._hasna else self._data.dtype
-        return self.to_numpy(dtype=dtype, na_value=libmissing.NA).tolist()  # type: ignore[return-value]
+        return self.to_numpy(dtype=dtype, na_value=libmissing.NA).tolist()
 
     @overload
     def astype(self, dtype: npt.DTypeLike, copy: bool = ...) -> np.ndarray: ...
diff --git a/pandas/core/base.py b/pandas/core/base.py
index 8304af48c3..6cc28d4e46 100644
--- a/pandas/core/base.py
+++ b/pandas/core/base.py
@@ -875,7 +875,7 @@ class IndexOpsMixin(OpsMixin):
         >>> idx.to_list()
         [1, 2, 3]
         """
-        return self._values.tolist()  # type: ignore[return-value]
+        return self._values.tolist()
 
     to_list = tolist
 
diff --git a/pandas/core/groupby/generic.py b/pandas/core/groupby/generic.py
index a1c1163435..b520ad69aa 100644
--- a/pandas/core/groupby/generic.py
+++ b/pandas/core/groupby/generic.py
@@ -2142,7 +2142,7 @@ class DataFrameGroupBy(GroupBy[DataFrame]):
 
         if stacked_values.dtype == object:
             # We'll have the DataFrame constructor do inference
-            stacked_values = stacked_values.tolist()  # type: ignore[assignment]
+            stacked_values = stacked_values.tolist()
         result = self.obj._constructor(stacked_values, index=index, columns=columns)
 
         if not self.as_index:
diff --git a/pandas/core/groupby/groupby.py b/pandas/core/groupby/groupby.py
index f74c20eaa0..33539e8d29 100644
--- a/pandas/core/groupby/groupby.py
+++ b/pandas/core/groupby/groupby.py
@@ -142,6 +142,7 @@ from pandas.core.util.numba_ import (
 
 if TYPE_CHECKING:
     from pandas._libs.tslibs import BaseOffset
+    from pandas._libs.tslibs.timedeltas import Timedelta
     from pandas._typing import (
         Any,
         Concatenate,
@@ -3872,39 +3873,54 @@ class GroupBy(BaseGroupBy[NDFrameT]):
         )
 
     @final
-    def ewm(self, *args, **kwargs) -> ExponentialMovingWindowGroupby:
+    def ewm(
+        self,
+        com: float | None = None,
+        span: float | None = None,
+        halflife: float | str | Timedelta | None = None,
+        alpha: float | None = None,
+        min_periods: int | None = 0,
+        adjust: bool = True,
+        ignore_na: bool = False,
+        times: np.ndarray | Series | None = None,
+        method: str = "single",
+    ) -> ExponentialMovingWindowGroupby:
         """
         Return an ewm grouper, providing ewm functionality per group.
 
         Parameters
         ----------
-        *args : tuple
-            Positional arguments passed to the EWM window constructor.
-        **kwargs : dict
-            Keyword arguments passed to the EWM window constructor, such as:
-
-            com : float, optional
-                Specify decay in terms of center of mass.
-                ``span``, ``halflife``, and ``alpha`` are alternative ways to specify
-                decay.
-            span : float, optional
-                Specify decay in terms of span.
-            halflife : float, optional
-                Specify decay in terms of half-life.
-            alpha : float, optional
-                Specify smoothing factor directly.
-            min_periods : int, default 0
-                Minimum number of observations in the window required to have a value;
-                otherwise, result is ``np.nan``.
-            adjust : bool, default True
-                Divide by decaying adjustment factor to account for imbalance in
-                relative weights.
-            ignore_na : bool, default False
-                Ignore missing values when calculating weights.
-            times : str or array-like of datetime64, optional
-                Times corresponding to the observations.
-            axis : {0 or 'index', 1 or 'columns'}, default 0
-                Axis along which the EWM function is applied.
+        com : float, optional
+            Specify decay in terms of center of mass.
+            Alternative to ``span``, ``halflife``, and ``alpha``.
+
+        span : float, optional
+            Specify decay in terms of span.
+
+        halflife : float, str, or Timedelta, optional
+            Specify decay in terms of half-life.
+
+        alpha : float, optional
+            Specify smoothing factor directly.
+
+        min_periods : int, default 0
+            Minimum number of observations in the window required to have a value;
+            otherwise, result is ``np.nan``.
+
+        adjust : bool, default True
+            Divide by decaying adjustment factor to account for imbalance in
+            relative weights.
+
+        ignore_na : bool, default False
+            Ignore missing values when calculating weights.
+
+        times : str or array-like of datetime64, optional
+            Times corresponding to the observations.
+
+        method : {'single', 'table'}, default 'single'
+            Execute the operation per group independently (``'single'``) or over the
+            entire object before regrouping (``'table'``). Only applicable to
+            ``mean()``, and only when using ``engine='numba'``.
 
         Returns
         -------
@@ -3950,9 +3966,16 @@ class GroupBy(BaseGroupBy[NDFrameT]):
 
         return ExponentialMovingWindowGroupby(
             self._selected_obj,
-            *args,
+            com=com,
+            span=span,
+            halflife=halflife,
+            alpha=alpha,
+            min_periods=min_periods,
+            adjust=adjust,
+            ignore_na=ignore_na,
+            times=times,
+            method=method,
             _grouper=self._grouper,
-            **kwargs,
         )
 
     @final
diff --git a/pandas/io/formats/format.py b/pandas/io/formats/format.py
index 189dfc1dde..f1be0b41ad 100644
--- a/pandas/io/formats/format.py
+++ b/pandas/io/formats/format.py
@@ -1495,7 +1495,7 @@ class _Datetime64Formatter(_GenericArrayFormatter):
         fmt_values = values._format_native_types(
             na_rep=self.nat_rep, date_format=self.date_format
         )
-        return fmt_values.tolist()  # type: ignore[return-value]
+        return fmt_values.tolist()
 
 
 class _ExtensionArrayFormatter(_GenericArrayFormatter):
diff --git a/pandas/io/stata.py b/pandas/io/stata.py
index 34d95fb59a..cd290710dd 100644
--- a/pandas/io/stata.py
+++ b/pandas/io/stata.py
@@ -3196,8 +3196,8 @@ class StataStrLWriter:
         for o, (idx, row) in enumerate(selected.iterrows()):
             for j, (col, v) in enumerate(col_index):
                 val = row[col]
-                # Allow columns with mixed str and None (GH 23633)
-                val = "" if val is None else val
+                # Allow columns with mixed str and None or pd.NA (GH 23633)
+                val = "" if isna(val) else val
                 key = gso_table.get(val, None)
                 if key is None:
                     # Stata prefers human numbers
diff --git a/pandas/plotting/_matplotlib/style.py b/pandas/plotting/_matplotlib/style.py
index 6e343b176b..962f9711d9 100644
--- a/pandas/plotting/_matplotlib/style.py
+++ b/pandas/plotting/_matplotlib/style.py
@@ -273,7 +273,7 @@ def _random_color(column: int) -> list[float]:
     """Get a random color represented as a list of length 3"""
     # GH17525 use common._random_state to avoid resetting the seed
     rs = com.random_state(column)
-    return rs.rand(3).tolist()  # type: ignore[return-value]
+    return rs.rand(3).tolist()
 
 
 def _is_single_string_color(color: Color) -> bool:


Test patch:
diff --git a/pandas/tests/io/test_stata.py b/pandas/tests/io/test_stata.py
index 9288b98d79..e73de78847 100644
--- a/pandas/tests/io/test_stata.py
+++ b/pandas/tests/io/test_stata.py
@@ -2587,3 +2587,17 @@ def test_many_strl(temp_file, version):
     lbls = ["".join(v) for v in itertools.product(*([string.ascii_letters] * 3))]
     value_labels = {"col": {i: lbls[i] for i in range(n)}}
     df.to_stata(temp_file, value_labels=value_labels, version=version)
+
+
+@pytest.mark.parametrize("version", [117, 118, 119, None])
+def test_strl_missings(temp_file, version):
+    # GH 23633
+    # Check that strl supports None and pd.NA
+    df = DataFrame(
+        [
+            {"str1": "string" * 500, "number": 0},
+            {"str1": None, "number": 1},
+            {"str1": pd.NA, "number": 1},
+        ]
+    )
+    df.to_stata(temp_file, version=version)
diff --git a/pandas/tests/test_downstream.py b/pandas/tests/test_downstream.py
index 6282aecdfe..a7b1c56ff4 100644
--- a/pandas/tests/test_downstream.py
+++ b/pandas/tests/test_downstream.py
@@ -187,9 +187,9 @@ def test_yaml_dump(df):
     tm.assert_frame_equal(df, loaded2)
 
 
-@pytest.mark.parametrize("dependency", ["numpy", "dateutil"])
+@pytest.mark.parametrize("dependency", ["numpy", "dateutil", "tzdata"])
 def test_missing_required_dependency(monkeypatch, dependency):
-    # GH#61030
+    # GH#61030, GH61273
     original_import = __import__
     mock_error = ImportError(f"Mock error for {dependency}")