We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
BUG: feature_union can't concat different dataframe well
https://github.com/Zeroto521/my-data-toolkit/blob/8b6ec3ce2658626f265bf5e1bab5a7c0897a0787/dtoolkit/transformer.py#L55-L56

When we use transformers to handle data frames, some rows would be deleted.
So use the feature union transformer would cause the following problem.

```python
0   1.0  0.0  0.0  1.0  0.0  ...    0.070607     0.0   1.0    1.0     1.0
1   0.0  1.0  0.0  1.0  0.0  ...    0.000000     0.0   1.0    1.0     1.0
2   0.0  0.0  1.0  0.0  1.0  ...    0.853865     1.0   1.0    1.0     1.0
3   0.0  0.0  1.0  0.0  1.0  ...    0.279593     0.0   0.0    1.0     0.0
4   0.0  0.0  1.0  1.0  0.0  ...    1.000000     0.0   1.0    1.0     0.0
5   1.0  0.0  0.0  0.0  1.0  ...    0.566105     0.0   0.0    1.0     0.0
6   0.0  1.0  0.0  1.0  0.0  ...    0.007911     0.0   1.0    0.0     1.0
7   0.0  1.0  0.0  1.0  0.0  ...    0.220168     0.0   1.0    0.0     1.0
8   0.0  1.0  0.0  1.0  0.0  ...    0.242736     0.0   1.0    0.0     1.0
9   1.0  0.0  0.0  1.0  0.0  ...    0.491557     0.0   1.0    0.0     1.0
10  1.0  0.0  0.0  0.0  1.0  ...         NaN     NaN   NaN    NaN     NaN
11  NaN  NaN  NaN  NaN  NaN  ...    0.184352     0.0   1.0    0.0     1.0
```

We could see, row index 10 and 11 data have NaN.

To fix this, there should add a parameter to ignore the index then concat data.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `dtoolkit/transformer/sklearn.py`
Content:
```
1 from __future__ import annotations
2 
3 from textwrap import dedent
4 from typing import TYPE_CHECKING
5 
6 import numpy as np
7 import pandas as pd
8 from pandas.util._decorators import doc
9 from sklearn.pipeline import FeatureUnion as SKFeatureUnion
10 from sklearn.preprocessing import MinMaxScaler as SKMinMaxScaler
11 from sklearn.preprocessing import OneHotEncoder as SKOneHotEncoder
12 
13 from dtoolkit.accessor.dataframe import cols  # noqa
14 from dtoolkit.accessor.series import cols  # noqa
15 from dtoolkit.transformer._util import transform_array_to_frame
16 from dtoolkit.transformer._util import transform_series_to_frame
17 from dtoolkit.transformer.base import Transformer
18 
19 if TYPE_CHECKING:
20     from scipy.sparse import csr_matrix
21 
22     from dtoolkit._typing import SeriesOrFrame
23     from dtoolkit._typing import TwoDimArray
24 
25 
26 class FeatureUnion(SKFeatureUnion, Transformer):
27     """
28     Concatenates results of multiple transformer objects.
29 
30     See Also
31     --------
32     make_union
33         Convenience function for simplified feature union construction.
34 
35     Notes
36     -----
37     Different to :obj:`sklearn.pipeline.FeatureUnion`.
38     This would let :obj:`~pandas.DataFrame` in and
39     :obj:`~pandas.DataFrame` out.
40 
41     Examples
42     --------
43     >>> from dtoolkit.transformer import FeatureUnion
44     >>> from sklearn.decomposition import PCA, TruncatedSVD
45     >>> union = FeatureUnion([("pca", PCA(n_components=1)),
46     ...                       ("svd", TruncatedSVD(n_components=2))])
47     >>> X = [[0., 1., 3], [2., 2., 5]]
48     >>> union.fit_transform(X)
49     array([[ 1.5       ,  3.0...,  0.8...],
50            [-1.5       ,  5.7..., -0.4...]])
51     """
52 
53     def _hstack(self, Xs):
54         if all(isinstance(i, (pd.Series, pd.DataFrame)) for i in Xs):
55             Xs = (i.reset_index(drop=True) for i in Xs)
56             return pd.concat(Xs, axis=1)
57 
58         return super()._hstack(Xs)
59 
60 
61 def make_union(
62     *transformers: list[Transformer],
63     n_jobs: int | None = None,
64     verbose: bool = False,
65 ) -> FeatureUnion:
66     """
67     Construct a FeatureUnion from the given transformers.
68 
69     See Also
70     --------
71     FeatureUnion
72         Class for concatenating the results of multiple transformer objects.
73 
74     Notes
75     -----
76     Different to :obj:`sklearn.pipeline.make_union`.
77     This would let :obj:`~pandas.DataFrame` in and
78     :obj:`~pandas.DataFrame` out.
79 
80     Examples
81     --------
82     >>> from sklearn.decomposition import PCA, TruncatedSVD
83     >>> from dtoolkit.transformer import make_union
84     >>> make_union(PCA(), TruncatedSVD())
85      FeatureUnion(transformer_list=[('pca', PCA()),
86                                    ('truncatedsvd', TruncatedSVD())])
87     """
88     from sklearn.pipeline import _name_estimators
89 
90     return FeatureUnion(
91         _name_estimators(transformers),
92         n_jobs=n_jobs,
93         verbose=verbose,
94     )
95 
96 
97 class MinMaxScaler(SKMinMaxScaler):
98     """
99     Transform features by scaling each feature to a given range.
100 
101     The transformation is given by::
102 
103         X_std = (X - X.min(axis=0)) / (X.max(axis=0) - X.min(axis=0))
104         X_scaled = X_std * (max - min) + min
105 
106     where :exc:`min, max = feature_range`.
107 
108     Examples
109     --------
110     >>> from dtoolkit.transformer import MinMaxScaler
111     >>> data = [[-1, 2], [-0.5, 6], [0, 10], [1, 18]]
112     >>> scaler = MinMaxScaler()
113     >>> scaler.fit(data)
114     MinMaxScaler()
115     >>> scaler.data_max_
116     array([ 1., 18.])
117     >>> scaler.transform(data)
118     array([[0.  , 0.  ],
119            [0.25, 0.25],
120            [0.5 , 0.5 ],
121            [1.  , 1.  ]])
122     >>> scaler.transform([[2, 2]])
123     array([[1.5, 0. ]])
124 
125     Notes
126     -----
127     Different to :obj:`sklearn.preprocessing.MinMaxScaler`.
128     This would let :obj:`~pandas.DataFrame` in and
129     :obj:`~pandas.DataFrame` out.
130     """
131 
132     def transform(self, X: TwoDimArray) -> TwoDimArray:
133         """
134         Scale features of X according to feature_range.
135 
136         Parameters
137         ----------
138         X : DataFrame or array-like of shape `(n_samples, n_features)`
139             Input data that will be transformed.
140 
141         Returns
142         -------
143         DataFrame or ndarray of shape `(n_samples, n_features)`
144             Transformed data.
145 
146         Notes
147         -----
148         This would let :obj:`~pandas.DataFrame` in and
149         :obj:`~pandas.DataFrame` out.
150         """
151 
152         X_new = super().transform(X)
153 
154         return transform_array_to_frame(X_new, X)
155 
156     def inverse_transform(self, X: SeriesOrFrame | np.ndarray) -> TwoDimArray:
157         """
158         Undo the scaling of X according to feature_range.
159 
160         Parameters
161         ----------
162         X : Series, DataFrame or array-like of shape `(n_samples, n_features)`
163             Input data that will be transformed. It cannot be sparse.
164 
165         Returns
166         -------
167         DataFrame or ndarray of shape (n_samples, n_features)
168             Transformed data.
169 
170         Notes
171         -----
172         This would let :obj:`~pandas.DataFrame` in and
173         :obj:`~pandas.DataFrame` out.
174         """
175 
176         X = transform_series_to_frame(X)
177         X_new = super().inverse_transform(X)
178 
179         return transform_array_to_frame(X_new, X)
180 
181 
182 class OneHotEncoder(SKOneHotEncoder):
183     """
184     Encode categorical features as a one-hot numeric array.
185 
186     Parameters
187     ----------
188     categories_with_parent : bool, default False
189         Returned column would hook parent labels if ``True`` else
190         would be ``categories``.
191 
192     sparse : bool, default False
193         Will return sparse matrix if ``True`` else will return an array.
194 
195     kwargs
196         See :obj:`sklearn.preprocessing.OneHotEncoder`.
197 
198     Notes
199     -----
200     Different to :obj:`sklearn.preprocessing.OneHotEncoder`.
201     The result would return a :obj:`~pandas.DataFrame` which uses categories
202     as columns.
203 
204     Examples
205     --------
206     Given a dataset with two features, we let the encoder find the unique
207     values per feature and transform the data to a binary one-hot encoding.
208 
209     :obj:`~pandas.DataFrame` in, :obj:`~pandas.DataFrame` out with categories
210     as columns.
211 
212     >>> from dtoolkit.transformer import OneHotEncoder
213     >>> import pandas as pd
214     >>> X = [['Male', 1], ['Female', 3], ['Female', 2]]
215     >>> df = pd.DataFrame(X, columns=['gender', 'number'])
216     >>> df
217         gender  number
218     0    Male       1
219     1  Female       3
220     2  Female       2
221     >>> enc = OneHotEncoder()
222     >>> enc.fit_transform(df)
223        Female  Male    1    2    3
224     0     0.0   1.0  1.0  0.0  0.0
225     1     1.0   0.0  0.0  0.0  1.0
226     2     1.0   0.0  0.0  1.0  0.0
227 
228     The encoded data also could hook parent labels.
229 
230     >>> enc = OneHotEncoder(categories_with_parent=True)
231     >>> enc.fit_transform(df)
232        gender_Female  gender_Male  number_1  number_2  number_3
233     0            0.0          1.0       1.0       0.0       0.0
234     1            1.0          0.0       0.0       0.0       1.0
235     2            1.0          0.0       0.0       1.0       0.0
236     """
237 
238     @doc(SKOneHotEncoder.__init__)
239     def __init__(
240         self,
241         categories_with_parent: bool = False,
242         sparse: bool = False,
243         **kwargs,
244     ):
245         super().__init__(sparse=sparse, **kwargs)
246         self.categories_with_parent = categories_with_parent
247 
248     @doc(
249         SKOneHotEncoder.transform,
250         dedent(
251             """
252         Notes
253         -----
254         This would let :obj:`~pandas.DataFrame` out.
255         """,
256         ),
257     )
258     def transform(self, X: TwoDimArray) -> TwoDimArray | csr_matrix:
259         from itertools import chain
260 
261         X_new = super().transform(X)
262 
263         if self.sparse is False:
264             categories = (
265                 self.get_feature_names(X.cols())
266                 if self.categories_with_parent
267                 else chain.from_iterable(self.categories_)
268             )
269 
270             return pd.DataFrame(X_new, columns=categories)
271 
272         return X_new
273
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/dtoolkit/transformer/sklearn.py b/dtoolkit/transformer/sklearn.py
--- a/dtoolkit/transformer/sklearn.py
+++ b/dtoolkit/transformer/sklearn.py
@@ -52,8 +52,8 @@
 
     def _hstack(self, Xs):
         if all(isinstance(i, (pd.Series, pd.DataFrame)) for i in Xs):
-            Xs = (i.reset_index(drop=True) for i in Xs)
-            return pd.concat(Xs, axis=1)
+            # merge all into one DataFrame and the index would use the common part
+            return pd.concat(Xs, axis=1, join="inner")
 
         return super()._hstack(Xs)

{"golden_diff": "diff --git a/dtoolkit/transformer/sklearn.py b/dtoolkit/transformer/sklearn.py\n--- a/dtoolkit/transformer/sklearn.py\n+++ b/dtoolkit/transformer/sklearn.py\n@@ -52,8 +52,8 @@\n \n     def _hstack(self, Xs):\n         if all(isinstance(i, (pd.Series, pd.DataFrame)) for i in Xs):\n-            Xs = (i.reset_index(drop=True) for i in Xs)\n-            return pd.concat(Xs, axis=1)\n+            # merge all into one DataFrame and the index would use the common part\n+            return pd.concat(Xs, axis=1, join=\"inner\")\n \n         return super()._hstack(Xs)\n", "issue": "BUG: feature_union can't concat different dataframe well\nhttps://github.com/Zeroto521/my-data-toolkit/blob/8b6ec3ce2658626f265bf5e1bab5a7c0897a0787/dtoolkit/transformer.py#L55-L56\r\n\r\nWhen we use transformers to handle data frames, some rows would be deleted.\r\nSo use the feature union transformer would cause the following problem.\r\n\r\n```python\r\n0   1.0  0.0  0.0  1.0  0.0  ...    0.070607     0.0   1.0    1.0     1.0\r\n1   0.0  1.0  0.0  1.0  0.0  ...    0.000000     0.0   1.0    1.0     1.0\r\n2   0.0  0.0  1.0  0.0  1.0  ...    0.853865     1.0   1.0    1.0     1.0\r\n3   0.0  0.0  1.0  0.0  1.0  ...    0.279593     0.0   0.0    1.0     0.0\r\n4   0.0  0.0  1.0  1.0  0.0  ...    1.000000     0.0   1.0    1.0     0.0\r\n5   1.0  0.0  0.0  0.0  1.0  ...    0.566105     0.0   0.0    1.0     0.0\r\n6   0.0  1.0  0.0  1.0  0.0  ...    0.007911     0.0   1.0    0.0     1.0\r\n7   0.0  1.0  0.0  1.0  0.0  ...    0.220168     0.0   1.0    0.0     1.0\r\n8   0.0  1.0  0.0  1.0  0.0  ...    0.242736     0.0   1.0    0.0     1.0\r\n9   1.0  0.0  0.0  1.0  0.0  ...    0.491557     0.0   1.0    0.0     1.0\r\n10  1.0  0.0  0.0  0.0  1.0  ...         NaN     NaN   NaN    NaN     NaN\r\n11  NaN  NaN  NaN  NaN  NaN  ...    0.184352     0.0   1.0    0.0     1.0\r\n```\r\n\r\nWe could see, row index 10 and 11 data have NaN.\r\n\r\nTo fix this, there should add a parameter to ignore the index then concat data.\n", "before_files": [{"content": "from __future__ import annotations\n\nfrom textwrap import dedent\nfrom typing import TYPE_CHECKING\n\nimport numpy as np\nimport pandas as pd\nfrom pandas.util._decorators import doc\nfrom sklearn.pipeline import FeatureUnion as SKFeatureUnion\nfrom sklearn.preprocessing import MinMaxScaler as SKMinMaxScaler\nfrom sklearn.preprocessing import OneHotEncoder as SKOneHotEncoder\n\nfrom dtoolkit.accessor.dataframe import cols  # noqa\nfrom dtoolkit.accessor.series import cols  # noqa\nfrom dtoolkit.transformer._util import transform_array_to_frame\nfrom dtoolkit.transformer._util import transform_series_to_frame\nfrom dtoolkit.transformer.base import Transformer\n\nif TYPE_CHECKING:\n    from scipy.sparse import csr_matrix\n\n    from dtoolkit._typing import SeriesOrFrame\n    from dtoolkit._typing import TwoDimArray\n\n\nclass FeatureUnion(SKFeatureUnion, Transformer):\n    \"\"\"\n    Concatenates results of multiple transformer objects.\n\n    See Also\n    --------\n    make_union\n        Convenience function for simplified feature union construction.\n\n    Notes\n    -----\n    Different to :obj:`sklearn.pipeline.FeatureUnion`.\n    This would let :obj:`~pandas.DataFrame` in and\n    :obj:`~pandas.DataFrame` out.\n\n    Examples\n    --------\n    >>> from dtoolkit.transformer import FeatureUnion\n    >>> from sklearn.decomposition import PCA, TruncatedSVD\n    >>> union = FeatureUnion([(\"pca\", PCA(n_components=1)),\n    ...                       (\"svd\", TruncatedSVD(n_components=2))])\n    >>> X = [[0., 1., 3], [2., 2., 5]]\n    >>> union.fit_transform(X)\n    array([[ 1.5       ,  3.0...,  0.8...],\n           [-1.5       ,  5.7..., -0.4...]])\n    \"\"\"\n\n    def _hstack(self, Xs):\n        if all(isinstance(i, (pd.Series, pd.DataFrame)) for i in Xs):\n            Xs = (i.reset_index(drop=True) for i in Xs)\n            return pd.concat(Xs, axis=1)\n\n        return super()._hstack(Xs)\n\n\ndef make_union(\n    *transformers: list[Transformer],\n    n_jobs: int | None = None,\n    verbose: bool = False,\n) -> FeatureUnion:\n    \"\"\"\n    Construct a FeatureUnion from the given transformers.\n\n    See Also\n    --------\n    FeatureUnion\n        Class for concatenating the results of multiple transformer objects.\n\n    Notes\n    -----\n    Different to :obj:`sklearn.pipeline.make_union`.\n    This would let :obj:`~pandas.DataFrame` in and\n    :obj:`~pandas.DataFrame` out.\n\n    Examples\n    --------\n    >>> from sklearn.decomposition import PCA, TruncatedSVD\n    >>> from dtoolkit.transformer import make_union\n    >>> make_union(PCA(), TruncatedSVD())\n     FeatureUnion(transformer_list=[('pca', PCA()),\n                                   ('truncatedsvd', TruncatedSVD())])\n    \"\"\"\n    from sklearn.pipeline import _name_estimators\n\n    return FeatureUnion(\n        _name_estimators(transformers),\n        n_jobs=n_jobs,\n        verbose=verbose,\n    )\n\n\nclass MinMaxScaler(SKMinMaxScaler):\n    \"\"\"\n    Transform features by scaling each feature to a given range.\n\n    The transformation is given by::\n\n        X_std = (X - X.min(axis=0)) / (X.max(axis=0) - X.min(axis=0))\n        X_scaled = X_std * (max - min) + min\n\n    where :exc:`min, max = feature_range`.\n\n    Examples\n    --------\n    >>> from dtoolkit.transformer import MinMaxScaler\n    >>> data = [[-1, 2], [-0.5, 6], [0, 10], [1, 18]]\n    >>> scaler = MinMaxScaler()\n    >>> scaler.fit(data)\n    MinMaxScaler()\n    >>> scaler.data_max_\n    array([ 1., 18.])\n    >>> scaler.transform(data)\n    array([[0.  , 0.  ],\n           [0.25, 0.25],\n           [0.5 , 0.5 ],\n           [1.  , 1.  ]])\n    >>> scaler.transform([[2, 2]])\n    array([[1.5, 0. ]])\n\n    Notes\n    -----\n    Different to :obj:`sklearn.preprocessing.MinMaxScaler`.\n    This would let :obj:`~pandas.DataFrame` in and\n    :obj:`~pandas.DataFrame` out.\n    \"\"\"\n\n    def transform(self, X: TwoDimArray) -> TwoDimArray:\n        \"\"\"\n        Scale features of X according to feature_range.\n\n        Parameters\n        ----------\n        X : DataFrame or array-like of shape `(n_samples, n_features)`\n            Input data that will be transformed.\n\n        Returns\n        -------\n        DataFrame or ndarray of shape `(n_samples, n_features)`\n            Transformed data.\n\n        Notes\n        -----\n        This would let :obj:`~pandas.DataFrame` in and\n        :obj:`~pandas.DataFrame` out.\n        \"\"\"\n\n        X_new = super().transform(X)\n\n        return transform_array_to_frame(X_new, X)\n\n    def inverse_transform(self, X: SeriesOrFrame | np.ndarray) -> TwoDimArray:\n        \"\"\"\n        Undo the scaling of X according to feature_range.\n\n        Parameters\n        ----------\n        X : Series, DataFrame or array-like of shape `(n_samples, n_features)`\n            Input data that will be transformed. It cannot be sparse.\n\n        Returns\n        -------\n        DataFrame or ndarray of shape (n_samples, n_features)\n            Transformed data.\n\n        Notes\n        -----\n        This would let :obj:`~pandas.DataFrame` in and\n        :obj:`~pandas.DataFrame` out.\n        \"\"\"\n\n        X = transform_series_to_frame(X)\n        X_new = super().inverse_transform(X)\n\n        return transform_array_to_frame(X_new, X)\n\n\nclass OneHotEncoder(SKOneHotEncoder):\n    \"\"\"\n    Encode categorical features as a one-hot numeric array.\n\n    Parameters\n    ----------\n    categories_with_parent : bool, default False\n        Returned column would hook parent labels if ``True`` else\n        would be ``categories``.\n\n    sparse : bool, default False\n        Will return sparse matrix if ``True`` else will return an array.\n\n    kwargs\n        See :obj:`sklearn.preprocessing.OneHotEncoder`.\n\n    Notes\n    -----\n    Different to :obj:`sklearn.preprocessing.OneHotEncoder`.\n    The result would return a :obj:`~pandas.DataFrame` which uses categories\n    as columns.\n\n    Examples\n    --------\n    Given a dataset with two features, we let the encoder find the unique\n    values per feature and transform the data to a binary one-hot encoding.\n\n    :obj:`~pandas.DataFrame` in, :obj:`~pandas.DataFrame` out with categories\n    as columns.\n\n    >>> from dtoolkit.transformer import OneHotEncoder\n    >>> import pandas as pd\n    >>> X = [['Male', 1], ['Female', 3], ['Female', 2]]\n    >>> df = pd.DataFrame(X, columns=['gender', 'number'])\n    >>> df\n        gender  number\n    0    Male       1\n    1  Female       3\n    2  Female       2\n    >>> enc = OneHotEncoder()\n    >>> enc.fit_transform(df)\n       Female  Male    1    2    3\n    0     0.0   1.0  1.0  0.0  0.0\n    1     1.0   0.0  0.0  0.0  1.0\n    2     1.0   0.0  0.0  1.0  0.0\n\n    The encoded data also could hook parent labels.\n\n    >>> enc = OneHotEncoder(categories_with_parent=True)\n    >>> enc.fit_transform(df)\n       gender_Female  gender_Male  number_1  number_2  number_3\n    0            0.0          1.0       1.0       0.0       0.0\n    1            1.0          0.0       0.0       0.0       1.0\n    2            1.0          0.0       0.0       1.0       0.0\n    \"\"\"\n\n    @doc(SKOneHotEncoder.__init__)\n    def __init__(\n        self,\n        categories_with_parent: bool = False,\n        sparse: bool = False,\n        **kwargs,\n    ):\n        super().__init__(sparse=sparse, **kwargs)\n        self.categories_with_parent = categories_with_parent\n\n    @doc(\n        SKOneHotEncoder.transform,\n        dedent(\n            \"\"\"\n        Notes\n        -----\n        This would let :obj:`~pandas.DataFrame` out.\n        \"\"\",\n        ),\n    )\n    def transform(self, X: TwoDimArray) -> TwoDimArray | csr_matrix:\n        from itertools import chain\n\n        X_new = super().transform(X)\n\n        if self.sparse is False:\n            categories = (\n                self.get_feature_names(X.cols())\n                if self.categories_with_parent\n                else chain.from_iterable(self.categories_)\n            )\n\n            return pd.DataFrame(X_new, columns=categories)\n\n        return X_new\n", "path": "dtoolkit/transformer/sklearn.py"}], "after_files": [{"content": "from __future__ import annotations\n\nfrom textwrap import dedent\nfrom typing import TYPE_CHECKING\n\nimport numpy as np\nimport pandas as pd\nfrom pandas.util._decorators import doc\nfrom sklearn.pipeline import FeatureUnion as SKFeatureUnion\nfrom sklearn.preprocessing import MinMaxScaler as SKMinMaxScaler\nfrom sklearn.preprocessing import OneHotEncoder as SKOneHotEncoder\n\nfrom dtoolkit.accessor.dataframe import cols  # noqa\nfrom dtoolkit.accessor.series import cols  # noqa\nfrom dtoolkit.transformer._util import transform_array_to_frame\nfrom dtoolkit.transformer._util import transform_series_to_frame\nfrom dtoolkit.transformer.base import Transformer\n\nif TYPE_CHECKING:\n    from scipy.sparse import csr_matrix\n\n    from dtoolkit._typing import SeriesOrFrame\n    from dtoolkit._typing import TwoDimArray\n\n\nclass FeatureUnion(SKFeatureUnion, Transformer):\n    \"\"\"\n    Concatenates results of multiple transformer objects.\n\n    See Also\n    --------\n    make_union\n        Convenience function for simplified feature union construction.\n\n    Notes\n    -----\n    Different to :obj:`sklearn.pipeline.FeatureUnion`.\n    This would let :obj:`~pandas.DataFrame` in and\n    :obj:`~pandas.DataFrame` out.\n\n    Examples\n    --------\n    >>> from dtoolkit.transformer import FeatureUnion\n    >>> from sklearn.decomposition import PCA, TruncatedSVD\n    >>> union = FeatureUnion([(\"pca\", PCA(n_components=1)),\n    ...                       (\"svd\", TruncatedSVD(n_components=2))])\n    >>> X = [[0., 1., 3], [2., 2., 5]]\n    >>> union.fit_transform(X)\n    array([[ 1.5       ,  3.0...,  0.8...],\n           [-1.5       ,  5.7..., -0.4...]])\n    \"\"\"\n\n    def _hstack(self, Xs):\n        if all(isinstance(i, (pd.Series, pd.DataFrame)) for i in Xs):\n            # merge all into one DataFrame and the index would use the common part\n            return pd.concat(Xs, axis=1, join=\"inner\")\n\n        return super()._hstack(Xs)\n\n\ndef make_union(\n    *transformers: list[Transformer],\n    n_jobs: int | None = None,\n    verbose: bool = False,\n) -> FeatureUnion:\n    \"\"\"\n    Construct a FeatureUnion from the given transformers.\n\n    See Also\n    --------\n    FeatureUnion\n        Class for concatenating the results of multiple transformer objects.\n\n    Notes\n    -----\n    Different to :obj:`sklearn.pipeline.make_union`.\n    This would let :obj:`~pandas.DataFrame` in and\n    :obj:`~pandas.DataFrame` out.\n\n    Examples\n    --------\n    >>> from sklearn.decomposition import PCA, TruncatedSVD\n    >>> from dtoolkit.transformer import make_union\n    >>> make_union(PCA(), TruncatedSVD())\n     FeatureUnion(transformer_list=[('pca', PCA()),\n                                   ('truncatedsvd', TruncatedSVD())])\n    \"\"\"\n    from sklearn.pipeline import _name_estimators\n\n    return FeatureUnion(\n        _name_estimators(transformers),\n        n_jobs=n_jobs,\n        verbose=verbose,\n    )\n\n\nclass MinMaxScaler(SKMinMaxScaler):\n    \"\"\"\n    Transform features by scaling each feature to a given range.\n\n    The transformation is given by::\n\n        X_std = (X - X.min(axis=0)) / (X.max(axis=0) - X.min(axis=0))\n        X_scaled = X_std * (max - min) + min\n\n    where :exc:`min, max = feature_range`.\n\n    Examples\n    --------\n    >>> from dtoolkit.transformer import MinMaxScaler\n    >>> data = [[-1, 2], [-0.5, 6], [0, 10], [1, 18]]\n    >>> scaler = MinMaxScaler()\n    >>> scaler.fit(data)\n    MinMaxScaler()\n    >>> scaler.data_max_\n    array([ 1., 18.])\n    >>> scaler.transform(data)\n    array([[0.  , 0.  ],\n           [0.25, 0.25],\n           [0.5 , 0.5 ],\n           [1.  , 1.  ]])\n    >>> scaler.transform([[2, 2]])\n    array([[1.5, 0. ]])\n\n    Notes\n    -----\n    Different to :obj:`sklearn.preprocessing.MinMaxScaler`.\n    This would let :obj:`~pandas.DataFrame` in and\n    :obj:`~pandas.DataFrame` out.\n    \"\"\"\n\n    def transform(self, X: TwoDimArray) -> TwoDimArray:\n        \"\"\"\n        Scale features of X according to feature_range.\n\n        Parameters\n        ----------\n        X : DataFrame or array-like of shape `(n_samples, n_features)`\n            Input data that will be transformed.\n\n        Returns\n        -------\n        DataFrame or ndarray of shape `(n_samples, n_features)`\n            Transformed data.\n\n        Notes\n        -----\n        This would let :obj:`~pandas.DataFrame` in and\n        :obj:`~pandas.DataFrame` out.\n        \"\"\"\n\n        X_new = super().transform(X)\n\n        return transform_array_to_frame(X_new, X)\n\n    def inverse_transform(self, X: SeriesOrFrame | np.ndarray) -> TwoDimArray:\n        \"\"\"\n        Undo the scaling of X according to feature_range.\n\n        Parameters\n        ----------\n        X : Series, DataFrame or array-like of shape `(n_samples, n_features)`\n            Input data that will be transformed. It cannot be sparse.\n\n        Returns\n        -------\n        DataFrame or ndarray of shape (n_samples, n_features)\n            Transformed data.\n\n        Notes\n        -----\n        This would let :obj:`~pandas.DataFrame` in and\n        :obj:`~pandas.DataFrame` out.\n        \"\"\"\n\n        X = transform_series_to_frame(X)\n        X_new = super().inverse_transform(X)\n\n        return transform_array_to_frame(X_new, X)\n\n\nclass OneHotEncoder(SKOneHotEncoder):\n    \"\"\"\n    Encode categorical features as a one-hot numeric array.\n\n    Parameters\n    ----------\n    categories_with_parent : bool, default False\n        Returned column would hook parent labels if ``True`` else\n        would be ``categories``.\n\n    sparse : bool, default False\n        Will return sparse matrix if ``True`` else will return an array.\n\n    kwargs\n        See :obj:`sklearn.preprocessing.OneHotEncoder`.\n\n    Notes\n    -----\n    Different to :obj:`sklearn.preprocessing.OneHotEncoder`.\n    The result would return a :obj:`~pandas.DataFrame` which uses categories\n    as columns.\n\n    Examples\n    --------\n    Given a dataset with two features, we let the encoder find the unique\n    values per feature and transform the data to a binary one-hot encoding.\n\n    :obj:`~pandas.DataFrame` in, :obj:`~pandas.DataFrame` out with categories\n    as columns.\n\n    >>> from dtoolkit.transformer import OneHotEncoder\n    >>> import pandas as pd\n    >>> X = [['Male', 1], ['Female', 3], ['Female', 2]]\n    >>> df = pd.DataFrame(X, columns=['gender', 'number'])\n    >>> df\n        gender  number\n    0    Male       1\n    1  Female       3\n    2  Female       2\n    >>> enc = OneHotEncoder()\n    >>> enc.fit_transform(df)\n       Female  Male    1    2    3\n    0     0.0   1.0  1.0  0.0  0.0\n    1     1.0   0.0  0.0  0.0  1.0\n    2     1.0   0.0  0.0  1.0  0.0\n\n    The encoded data also could hook parent labels.\n\n    >>> enc = OneHotEncoder(categories_with_parent=True)\n    >>> enc.fit_transform(df)\n       gender_Female  gender_Male  number_1  number_2  number_3\n    0            0.0          1.0       1.0       0.0       0.0\n    1            1.0          0.0       0.0       0.0       1.0\n    2            1.0          0.0       0.0       1.0       0.0\n    \"\"\"\n\n    @doc(SKOneHotEncoder.__init__)\n    def __init__(\n        self,\n        categories_with_parent: bool = False,\n        sparse: bool = False,\n        **kwargs,\n    ):\n        super().__init__(sparse=sparse, **kwargs)\n        self.categories_with_parent = categories_with_parent\n\n    @doc(\n        SKOneHotEncoder.transform,\n        dedent(\n            \"\"\"\n        Notes\n        -----\n        This would let :obj:`~pandas.DataFrame` out.\n        \"\"\",\n        ),\n    )\n    def transform(self, X: TwoDimArray) -> TwoDimArray | csr_matrix:\n        from itertools import chain\n\n        X_new = super().transform(X)\n\n        if self.sparse is False:\n            categories = (\n                self.get_feature_names(X.cols())\n                if self.categories_with_parent\n                else chain.from_iterable(self.categories_)\n            )\n\n            return pd.DataFrame(X_new, columns=categories)\n\n        return X_new\n", "path": "dtoolkit/transformer/sklearn.py"}]}