Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
4118156
created unit test reproducing issue 376; all test failing now until f…
noahjgreen295 Feb 19, 2022
0d5e625
added _check_for_X_y_index_mismatch() to dataframe_checks.py. Even th…
noahjgreen295 Feb 19, 2022
9ef98e7
calls to _check_for_X_y_index_mismatch(X, y) added to MeanEncoder, Wo…
noahjgreen295 Feb 19, 2022
07cf7c9
black/isort formatting
noahjgreen295 Feb 19, 2022
a41b6ee
additional formatting fixes needed for CI
noahjgreen295 Feb 19, 2022
7556309
moved test_fix_index_mismatch_from_upstream_array() out of separate f…
noahjgreen295 Feb 22, 2022
8916bf0
following suggestion in PR feedback to simplify test and remove depen…
noahjgreen295 Feb 22, 2022
db50fe4
forgot to remove import
noahjgreen295 Feb 22, 2022
8667696
added a few more encoders to unit test that were not having issue but…
noahjgreen295 Feb 22, 2022
28a2edd
isort/black and other reformatting
noahjgreen295 Feb 22, 2022
261190d
changed fixture to have different y values for different encoder test…
noahjgreen295 Feb 22, 2022
553f73d
change to test_fix_index_mismatch_from_upstream_array() to illustrate…
noahjgreen295 Mar 27, 2022
23929a8
Revert "change to test_fix_index_mismatch_from_upstream_array() to il…
noahjgreen295 Mar 27, 2022
5b2d686
refactored and renamed original unit test to get into form where we c…
noahjgreen295 Mar 27, 2022
2f998b7
added new unit test for case where y is the array and X is the pandas…
noahjgreen295 Mar 28, 2022
d74d143
renamed _check_for_X_y_index_mismatch() to _check_X_y_pd_np_mismatch(…
noahjgreen295 Mar 28, 2022
7c274dd
black/isort/flake8 etc. fixes
noahjgreen295 Mar 28, 2022
dd901f5
changed unit tests to assert on expected values, rather than all non-…
noahjgreen295 Mar 28, 2022
248edd9
added fix to OrdinalEncoder; all unit tests now passing
noahjgreen295 Mar 28, 2022
39990ac
standardized ordering of test parameterizations for both new unit tests
noahjgreen295 Mar 28, 2022
8377b3b
black/isort/flake8 changes
noahjgreen295 Mar 28, 2022
b298711
did away with logic in _check_X_y_pd_np_mismatch() where X could be a…
noahjgreen295 Mar 28, 2022
ccb390d
changed _check_X_y_pd_np_mismatch() to raise error when DataFrame X a…
noahjgreen295 Mar 29, 2022
e4eafce
added call to _check_X_y_pd_np_mismatch() in fit because tests were f…
noahjgreen295 Mar 29, 2022
7dd69e9
added unit test to assert exception in case where DataFrame X and Ser…
noahjgreen295 Mar 29, 2022
fa4f14f
black/isort/flake8
noahjgreen295 Mar 29, 2022
c121ca6
minor flake8
noahjgreen295 Mar 29, 2022
a02a1da
renamed _check_X_y_pd_np_mismatch() to _check_X_y() as per feedback a…
noahjgreen295 Mar 30, 2022
f90c63f
added numpy_to_pandas functionality, subroutines to be used in severa…
noahjgreen295 Mar 31, 2022
030c3eb
rewrote _check_X_y() to handle all specified cases; new unit test sui…
noahjgreen295 Apr 2, 2022
e1abfd8
base encoder: remove is_dataframe from _check_fit_input_and_variables…
noahjgreen295 Apr 2, 2022
d0c4401
for the encoders that do not require y, add is_dataframe() when needed
noahjgreen295 Apr 2, 2022
81613b6
for the encoders that do require y, check_X_y() handles everything, i…
noahjgreen295 Apr 2, 2022
3788b23
OrdinalEncoder is special, because it should work with and without y
noahjgreen295 Apr 2, 2022
a2dc494
removed unneccessary checking/converting of y to Series now handled b…
noahjgreen295 Apr 2, 2022
7e00ef5
_is_dataframe() now uses functionality/subroutines from numpy_to_pand…
noahjgreen295 Apr 2, 2022
ccfea67
renamed the new _check_X_y() to _check_pd_X_y(), in order to avoid na…
noahjgreen295 Apr 2, 2022
784dd21
black/isort/flake8
noahjgreen295 Apr 2, 2022
d5ea00c
additional flake8
noahjgreen295 Apr 2, 2022
c3e8fd8
minor flake8
noahjgreen295 Apr 2, 2022
ad23215
cleaned up some cruftiness in numpy_to_pandas
noahjgreen295 Apr 2, 2022
2337ca1
black/isort/flake8
noahjgreen295 Apr 2, 2022
5156bd1
fixed incorrect merge that I had done after git pull upstream
noahjgreen295 Apr 3, 2022
869d114
black/isort/flake8
noahjgreen295 Apr 3, 2022
1d05f95
fixes to _check_pd_X_y() to fix unit test after git pull upstream and…
noahjgreen295 Apr 3, 2022
4f3c343
broke up test_check_pd_X_y() into 3 separate test functions: test_che…
noahjgreen295 Apr 4, 2022
07e7a18
black/isort/flake8
noahjgreen295 Apr 4, 2022
e96af86
broke up further... both_same_type now both_numpy and both_pandas
noahjgreen295 Apr 4, 2022
fafbea0
black/isort/flake8
noahjgreen295 Apr 4, 2022
12cc894
fixed type hint and docstring on _check_fit_input_and_variables()
noahjgreen295 Apr 5, 2022
f953e91
confirmed that incompatible lengths also raise error in _check_pd_X_y
noahjgreen295 Apr 6, 2022
c24514b
_check_pd_X_y() now copies any incoming pandas objects; fixed unit te…
noahjgreen295 Apr 6, 2022
a92d4b0
_check_pd_X_y() raises exception if either incoming object is None or…
noahjgreen295 Apr 6, 2022
709ac24
_check_pd_X_y() now supports list or Tuple for y; unit tests to confirm
noahjgreen295 Apr 6, 2022
f2b5ad9
black/isort/flake8
noahjgreen295 Apr 6, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 88 additions & 4 deletions feature_engine/dataframe_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
transform().
"""

from typing import List, Union
from typing import List, Tuple, Union

import numpy as np
import pandas as pd
from scipy.sparse import issparse

from .numpy_to_pandas import _is_numpy, _numpy_to_dataframe, _numpy_to_series


def _is_dataframe(X: pd.DataFrame) -> pd.DataFrame:
"""
Expand All @@ -33,9 +35,8 @@ def _is_dataframe(X: pd.DataFrame) -> pd.DataFrame:
"""
# check_estimator uses numpy arrays for its checks.
# Thus, we need to allow np arrays
if isinstance(X, (np.generic, np.ndarray)):
col_names = [str(i) for i in range(X.shape[1])]
X = pd.DataFrame(X, columns=col_names)
if _is_numpy(X):
X = _numpy_to_dataframe(X)

if issparse(X):
raise ValueError("This transformer does not support sparse matrices.")
Expand Down Expand Up @@ -129,3 +130,86 @@ def _check_contains_inf(X: pd.DataFrame, variables: List[Union[str, int]]) -> No
"Some of the variables to transform contain inf values. Check and "
"remove those before using this transformer."
)


def _check_pd_X_y(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi guys, I spent a good few hours going over this function, and I still can't decide what is best.

The thing is, if we are going to replace is_dataframe with this function, then the function should be able to return a copy of the df (like is_dataframe) and potentially a copy of y (not sure is necessary), to avoid inadvertently modifying the user's data. So before merging, at least, we need to ensure we return a copy of X.

When playing around with this function, I found out some errors in the tests raised by check_estimator. One of the tests would be if y=None, and I've noticed that we do not handle this situation. At the moment, we are assuming that the user would enter a numpy array or a series, but in theory, they could also enter a list, a tuple or None, and all of that would work just fine with this version of check_pd_x_y and still fail in the concatenation, unless we ensure we return a pd.series from this function.

But, pd.Series(None) would still return a pd.series. And if we do pd.Series(None, index=X.index) it will return a pd series of the length of the dataframe full of nan, when the user enters None.

If I convert a numpy array to a df, and force the index of the dataframe, what if the array was shorter than the df? would it fail or would it introduce Nan? I did not check this, so thinking out loud.

I then went ahead and checked the check_x_y function from sklearn for some inspiration, and I see that they handle the y=None at the very top of the check. So we probably should do that as well.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I convert a numpy array to a df, and force the index of the dataframe, what if the array was shorter than the df? would it fail or would it introduce Nan? I did not check this, so thinking out loud.

Pretty sure I check for incompatible dimensions and unit test it, but I'll double check.

I'll go ahead and make the following changes:

  • Return copies of X and y when they are incoming as pandas objects
  • Raise an exception if y (or X) is None
  • Accommodate other array-like inputs for y

Sound good?

X: Union[pd.DataFrame, np.ndarray],
y: Union[pd.Series, np.ndarray, list, Tuple],
):
"""
Returns X as a DataFrame and y as a Series, converting any numpy
objects to pandas objects as needed.
* If both parameters are numpy objects, they are converted to pandas objects.
* If one parameter is a pandas object and the other is a numpy object,
the former will be converted to a pandas object, with the indexes
of the latter.
* If both parameters are pandas objects, and their indexes are inconsistent,
an exception is raised (i.e. this is the caller's error.)
* If both parameters are pandas objects and their indexes match, they are
copied and returned.
* If X is sparse or X is empty or, after all transforms, is stiil
not a DataFrame, raises an exception
* Raises an exception if either incoming object is None or empty

Parameters
----------
X: Pandas DataFrame or numpy ndarray
y: Pandas Series or numpy ndarray or list or tuple

Returns
-------
X: Pandas DataFrame
y: Pandas Series

Exceptions
----------
ValueError: if X and y are dimension-incompatible, X and y are pandas objects
with inconsistent indexes, or if either X or y is None/empty
"""
# * Raises an exception if either incoming object is None or empty
if X is None or len(X) == 0:
raise ValueError("X cannot be None or empty")
if y is None or len(y) == 0:
raise ValueError("y cannot be None or empty")

# * If both parameters are numpy objects, they are converted to pandas objects.
# * If one parameter is a pandas object and the other is a numpy object,
# the former will be converted to a pandas object, with the indexes
# of the latter. (Lists and tuples are also supported for y)
if _is_numpy(X):
X = _numpy_to_dataframe(X, index=y.index if isinstance(y, pd.Series) else None)
if _is_numpy(y):
y = _numpy_to_series(y, index=X.index if isinstance(X, pd.DataFrame) else None)
if isinstance(y, (list, Tuple)):
y = pd.Series(y)
y.index = X.index if isinstance(X, pd.DataFrame) else None

# * If both parameters are pandas objects, and their indexes are inconsistent,
# an exception is raised (i.e. this is the caller's error.)
# * If both parameters are pandas objects and their indexes match, they are
# copied and returned
if isinstance(X, pd.DataFrame) and isinstance(y, pd.Series):
if not all(y.index == X.index):
raise ValueError("Index mismatch between DataFrame X and Series y")
else:
return X.copy(), y.copy()

# * If X is sparse or X is empty or, after all transforms, is stiil
# not a DataFrame, raises an exception
# (This deliberately carries out similar tests in _is_dataframe() above in
# order to support different code paths)
if issparse(X):
raise ValueError("This transformer does not support sparse matrices.")

if not isinstance(X, pd.DataFrame):
raise TypeError(
"X is not a pandas dataframe. The dataset should be a pandas dataframe."
)

if X.empty:
raise ValueError(
"0 feature(s) (shape=%s) while a minimum of %d is "
"required." % (X.shape, 1)
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could simplify this test a bit.

X can only be a pandas dataframe at this stage, because if it was an array, is_dataframe converted it to a df, and if it was something else that is not permitted, like a pd.Series, is_dataframe should have raised an error (if it does not, we need to fix is_dataframe).

So when we call this function within the classes, the only chance is X is a dataframe, and y can be, in theory, an array or a series.

I think sklearn also allows arrays as targets, but I don't think we can use that with the encoders. Our encoders are tailored to binary classification mostly. So we probably need that check as well :_(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, given the scenarios that I posed in #376 the best solution would be to replace _is_dataframe in the encoders by a new function that checks simultaneously X and y, because depending on the input combination we should raise errors or not.

thoughts?

@noahjgreen295 noahjgreen295 Mar 28, 2022

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking something like that earlier but was afraid to make changes to calls to _is_dataframe() :-) Now I know that's OK so will have a look tonight, from what I can tell this makes sense.

@noahjgreen295 noahjgreen295 Mar 28, 2022

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

X can only be a pandas dataframe at this stage, because if it was an array, is_dataframe converted it to a df

In commits from earlier, I deliberately put the call to _check_X_y_pd_np_mismatch() before the call to self._check_fit_input_and_variables() (and thus _is_dataframe()) so that X has the chance to arrive at _check_X_y_pd_np_mismatch() as an array. (For an example, see here ) This allows me to detect that particular error case.

and if it was something else that is not permitted, like a pd.Series, is_dataframe should have raised an error (if it does not, we need to fix is_dataframe)

This is correct. I'll remove the logic that handles cases of X being a Series since that is not possible.

I do feel good about merging the functionality of _check_X_y_pd_np_mismatch() into self._check_fit_input_and_variables(). However, I think it might mean some restructuring of the unit tests, so please confirm.

I have additional followup on the #376 page.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not change is_dataframe because it is used all over our codebase.

I would do the following:

if the encoder needs X and y, instead of using _is_dataframe, use directly your check and return a pandas dataframe and a pd series.

If encoder does not need y: then use is_dataframe as usual.

If encoder has the option to do both (OrdinalEncoder):

if self.encoding_method == "ordered":
    X, y = check_X_y_pd_np_mismatch(X, y)
else:
   X = is_dataframe(X)

This means, taking the _is_dataframe out of _check_input_and_variables() hidden method in the base_encoder and amending the code slightly in all the classes.

return X, y
14 changes: 2 additions & 12 deletions feature_engine/encoding/base_encoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def __init__(
self.variables = _check_input_parameter_variables(variables)
self.ignore_format = ignore_format

def _check_fit_input_and_variables(self, X: pd.DataFrame) -> pd.DataFrame:
def _check_fit_input_and_variables(self, X: pd.DataFrame):
"""
Checks that input is a dataframe, finds categorical variables, or alternatively
checks that the variables entered by the user are of type object (categorical).
Expand All @@ -71,18 +71,8 @@ def _check_fit_input_and_variables(self, X: pd.DataFrame) -> pd.DataFrame:
ValueError
If there are no categorical variables in the df or the df is empty
If the variable(s) contain null values

Returns
-------
X: Pandas DataFrame
The same dataframe entered as parameter
variables : list
list of categorical variables
"""

# check input dataframe
X = _is_dataframe(X)

if not self.ignore_format:
# find categorical variables or check variables entered by user are object
self.variables_: List[
Expand All @@ -101,7 +91,7 @@ def _check_fit_input_and_variables(self, X: pd.DataFrame) -> pd.DataFrame:
# save train set shape
self.n_features_in_ = X.shape[1]

return X
return self

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The typehint and docstring should be changed to match this updated return behavior. I might suggest not returning anything, but I did see @solegalli suggested specifically self.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK fixed in latest commit. Convention in rest of code for return self seems to be blank typehint and no mention in docstring, so I went with that.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bmreiniger your comment was very enlightening. Following sklearn convention, the fit method needs to return self. And I now found out that it might be to allow method cascading, so class.fit().transform() which would not be possible without returning self.

So indeed, there is no need to return self in this method, because we won't cascade it. In fact, it is an internal method.

Here is the reference I found, mostly for my information lol:
https://stackoverflow.com/questions/43380042/purpose-of-return-self-python

Thank you!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@solegalli OK should I change to not return self?


def _check_transform_input_and_state(self, X: pd.DataFrame) -> pd.DataFrame:
"""
Expand Down
4 changes: 3 additions & 1 deletion feature_engine/encoding/count_frequency.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import pandas as pd

from feature_engine.dataframe_checks import _is_dataframe
from feature_engine.docstrings import (
Substitution,
_feature_names_in_docstring,
Expand Down Expand Up @@ -139,7 +140,8 @@ def fit(self, X: pd.DataFrame, y: Optional[pd.Series] = None):
y is not needed in this encoder. You can pass y or None.
"""

X = self._check_fit_input_and_variables(X)
X = _is_dataframe(X)
self._check_fit_input_and_variables(X)

self.encoder_dict_ = {}

Expand Down
4 changes: 3 additions & 1 deletion feature_engine/encoding/decision_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from sklearn.pipeline import Pipeline
from sklearn.utils.multiclass import check_classification_targets, type_of_target

from feature_engine.dataframe_checks import _check_pd_X_y
from feature_engine.discretisation import DecisionTreeDiscretiser
from feature_engine.docstrings import (
Substitution,
Expand Down Expand Up @@ -202,7 +203,8 @@ def fit(self, X: pd.DataFrame, y: pd.Series):
check_classification_targets(y)

# check input dataframe
X = self._check_fit_input_and_variables(X)
X, y = _check_pd_X_y(X, y)
self._check_fit_input_and_variables(X)

if self.param_grid:
param_grid = self.param_grid
Expand Down
7 changes: 3 additions & 4 deletions feature_engine/encoding/mean_encoding.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import pandas as pd

from feature_engine.dataframe_checks import _check_pd_X_y
from feature_engine.docstrings import (
Substitution,
_feature_names_in_docstring,
Expand Down Expand Up @@ -131,10 +132,8 @@ def fit(self, X: pd.DataFrame, y: pd.Series):
The target.
"""

X = self._check_fit_input_and_variables(X)

if not isinstance(y, pd.Series):
y = pd.Series(y)
X, y = _check_pd_X_y(X, y)
self._check_fit_input_and_variables(X)

temp = pd.concat([X, y], axis=1)
temp.columns = list(X.columns) + ["target"]
Expand Down
4 changes: 3 additions & 1 deletion feature_engine/encoding/one_hot.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import pandas as pd
from sklearn.utils.validation import check_is_fitted

from feature_engine.dataframe_checks import _is_dataframe
from feature_engine.docstrings import (
Substitution,
_feature_names_in_docstring,
Expand Down Expand Up @@ -180,7 +181,8 @@ def fit(self, X: pd.DataFrame, y: Optional[pd.Series] = None):
None.
"""

X = self._check_fit_input_and_variables(X)
X = _is_dataframe(X)
self._check_fit_input_and_variables(X)

self.encoder_dict_ = {}

Expand Down
11 changes: 7 additions & 4 deletions feature_engine/encoding/ordinal.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import pandas as pd

from feature_engine.dataframe_checks import _check_pd_X_y, _is_dataframe
from feature_engine.docstrings import (
Substitution,
_feature_names_in_docstring,
Expand Down Expand Up @@ -146,16 +147,18 @@ def fit(self, X: pd.DataFrame, y: Optional[pd.Series] = None):
Otherwise, y needs to be passed when fitting the transformer.
"""

X = self._check_fit_input_and_variables(X)
# All dimension, type, etc. checking
if self.encoding_method == "ordered":
X, y = _check_pd_X_y(X, y)
else:
X = _is_dataframe(X)
self._check_fit_input_and_variables(X)

# join target to predictor variables
if self.encoding_method == "ordered":
if y is None:
raise ValueError("Please provide a target y for this encoding method")

if not isinstance(y, pd.Series):
y = pd.Series(y)

temp = pd.concat([X, y], axis=1)
temp.columns = list(X.columns) + ["target"]

Expand Down
7 changes: 3 additions & 4 deletions feature_engine/encoding/probability_ratio.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import numpy as np
import pandas as pd

from feature_engine.dataframe_checks import _check_pd_X_y
from feature_engine.docstrings import (
Substitution,
_feature_names_in_docstring,
Expand Down Expand Up @@ -154,10 +155,8 @@ def fit(self, X: pd.DataFrame, y: pd.Series):
Target, must be binary.
"""

X = self._check_fit_input_and_variables(X)

if not isinstance(y, pd.Series):
y = pd.Series(y)
X, y = _check_pd_X_y(X, y)
self._check_fit_input_and_variables(X)

# check that y is binary
if y.nunique() != 2:
Expand Down
4 changes: 3 additions & 1 deletion feature_engine/encoding/rare_label.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import numpy as np
import pandas as pd

from feature_engine.dataframe_checks import _is_dataframe
from feature_engine.docstrings import (
Substitution,
_feature_names_in_docstring,
Expand Down Expand Up @@ -147,7 +148,8 @@ def fit(self, X: pd.DataFrame, y: Optional[pd.Series] = None):
y is not required. You can pass y or None.
"""

X = self._check_fit_input_and_variables(X)
X = _is_dataframe(X)
self._check_fit_input_and_variables(X)

self.encoder_dict_ = {}

Expand Down
7 changes: 3 additions & 4 deletions feature_engine/encoding/woe.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import numpy as np
import pandas as pd

from feature_engine.dataframe_checks import _check_pd_X_y
from feature_engine.docstrings import (
Substitution,
_feature_names_in_docstring,
Expand Down Expand Up @@ -136,10 +137,8 @@ def fit(self, X: pd.DataFrame, y: pd.Series):
Target, must be binary.
"""

X = self._check_fit_input_and_variables(X)

if not isinstance(y, pd.Series):
y = pd.Series(y)
X, y = _check_pd_X_y(X, y)
self._check_fit_input_and_variables(X)

# check that y is binary
if y.nunique() != 2:
Expand Down
62 changes: 62 additions & 0 deletions feature_engine/numpy_to_pandas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""Functions to detect numpy objects and convert to pandas objects."""

from typing import Any, List, Union

import numpy as np
import pandas as pd


def _is_numpy(obj_in: Any) -> bool:
"""
Tests if an object is a numpy object.
If the input is a numpy array, it converts it to a pandas Dataframe. This is mostly
so that we can add the check_estimator checks for compatibility with sklearn.

Parameters
----------
obj_in : the object to test.

Returns
-------
True if object is a numpy object, else False
"""
return isinstance(obj_in, (np.generic, np.ndarray))


def _numpy_to_dataframe(
obj_in: Union[np.generic, np.ndarray], index=None
) -> pd.DataFrame:
"""
Converts a numpy object to a pandas DataFrame

Parameters
----------
obj_in : the object to convert
index : array-like (optional); will set index on DataFrame

Returns
-------
df_out : the object converted to a pandas DataFrame
"""
col_names: List[str] = [str(i) for i in range(obj_in.shape[1])]
df_out: pd.DataFrame = pd.DataFrame(obj_in, columns=col_names, index=index)

return df_out


def _numpy_to_series(obj_in: Union[np.generic, np.ndarray], index=None) -> pd.Series:
"""
Converts a numpy object to a pandas Series

Parameters
----------
obj_in : the object to convert
index : array-like (optional); will set index on Series

Returns
-------
df_out : the object converted to a pandas Series
"""
s_out: pd.Series = pd.Series(obj_in, index=index)

return s_out
Loading