Repository: statsmodels/statsmodels

Issue:
BUG: Fix missing str
Add str when appending using pandas wrapper

- [x] closes #6827
- [x] tests added / passed. 
- [x] code/documentation is well formatted.  
- [x] properly formatted commit message. See 
      [NumPy's guide](https://docs.scipy.org/doc/numpy-1.15.1/dev/gitwash/development_workflow.html#writing-the-commit-message). 

<details>


**Notes**:

* It is essential that you add a test when making code changes. Tests are not 
  needed for doc changes.
* When adding a new function, test values should usually be verified in another package (e.g., R/SAS/Stata).
* When fixing a bug, you must add a test that would produce the bug in master and
  then show that it is fixed with the new code.
* New code additions must be well formatted. Changes should pass flake8. If on Linux or OSX, you can
  verify you changes are well formatted by running 
  ```
  git diff upstream/master -u -- "*.py" | flake8 --diff --isolated
  ```
  assuming `flake8` is installed. This command is also available on Windows 
  using the Windows System for Linux once `flake8` is installed in the 
  local Linux environment. While passing this test is not required, it is good practice and it help 
  improve code quality in `statsmodels`.
* Docstring additions must render correctly, including escapes and LaTeX.

</details>



Discussion:

Resolving patch:
diff --git a/statsmodels/tools/validation/__init__.py b/statsmodels/tools/validation/__init__.py
--- a/statsmodels/tools/validation/__init__.py
+++ b/statsmodels/tools/validation/__init__.py
@@ -1,7 +1,22 @@
-from .validation import (array_like, bool_like,  dict_like,
-                         float_like, int_like, PandasWrapper,
-                         required_int_like, string_like)
+from .validation import (
+    array_like,
+    bool_like,
+    dict_like,
+    float_like,
+    int_like,
+    PandasWrapper,
+    required_int_like,
+    string_like,
+)
 
 
-__all__ = ['array_like', 'bool_like', 'dict_like', 'float_like', 'int_like',
-           'PandasWrapper', 'required_int_like', 'string_like']
+__all__ = [
+    "array_like",
+    "bool_like",
+    "dict_like",
+    "float_like",
+    "int_like",
+    "PandasWrapper",
+    "required_int_like",
+    "string_like",
+]
diff --git a/statsmodels/tools/validation/decorators.py b/statsmodels/tools/validation/decorators.py
--- a/statsmodels/tools/validation/decorators.py
+++ b/statsmodels/tools/validation/decorators.py
@@ -5,25 +5,37 @@
 import statsmodels.tools.validation.validation as v
 
 
-def array_like(pos, name, dtype=np.double, ndim=None, maxdim=None,
-               shape=None, order='C', contiguous=False):
+def array_like(
+    pos,
+    name,
+    dtype=np.double,
+    ndim=None,
+    maxdim=None,
+    shape=None,
+    order="C",
+    contiguous=False,
+):
     def inner(func):
         @wraps(func)
         def wrapper(*args, **kwargs):
             if pos < len(args):
                 arg = args[pos]
-                arg = v.array_like(arg, name, dtype, ndim, maxdim, shape,
-                                   order, contiguous)
+                arg = v.array_like(
+                    arg, name, dtype, ndim, maxdim, shape, order, contiguous
+                )
                 if pos == 0:
                     args = (arg,) + args[1:]
                 else:
                     args = args[:pos] + (arg,) + args[pos + 1:]
             else:
                 arg = kwargs[name]
-                arg = v.array_like(arg, name, dtype, ndim, maxdim, shape,
-                                   order, contiguous)
+                arg = v.array_like(
+                    arg, name, dtype, ndim, maxdim, shape, order, contiguous
+                )
                 kwargs[name] = arg
 
             return func(*args, **kwargs)
+
         return wrapper
+
     return inner
diff --git a/statsmodels/tools/validation/validation.py b/statsmodels/tools/validation/validation.py
--- a/statsmodels/tools/validation/validation.py
+++ b/statsmodels/tools/validation/validation.py
@@ -33,8 +33,17 @@ def _right_squeeze(arr, stop_dim=0):
     return arr.reshape(arr.shape[:last])
 
 
-def array_like(obj, name, dtype=np.double, ndim=1, maxdim=None,
-               shape=None, order=None, contiguous=False, optional=False):
+def array_like(
+    obj,
+    name,
+    dtype=np.double,
+    ndim=1,
+    maxdim=None,
+    shape=None,
+    order=None,
+    contiguous=False,
+    optional=False,
+):
     """
     Convert array-like to a ndarray and check conditions
 
@@ -125,7 +134,7 @@ def array_like(obj, name, dtype=np.double, ndim=1, maxdim=None,
     arr = np.asarray(obj, dtype=dtype, order=order)
     if maxdim is not None:
         if arr.ndim > maxdim:
-            msg = '{0} must have ndim <= {1}'.format(name, maxdim)
+            msg = "{0} must have ndim <= {1}".format(name, maxdim)
             raise ValueError(msg)
     elif ndim is not None:
         if arr.ndim > ndim:
@@ -133,13 +142,13 @@ def array_like(obj, name, dtype=np.double, ndim=1, maxdim=None,
         elif arr.ndim < ndim:
             arr = np.reshape(arr, arr.shape + (1,) * (ndim - arr.ndim))
         if arr.ndim != ndim:
-            msg = '{0} is required to have ndim {1} but has ndim {2}'
+            msg = "{0} is required to have ndim {1} but has ndim {2}"
             raise ValueError(msg.format(name, ndim, arr.ndim))
     if shape is not None:
         for actual, req in zip(arr.shape, shape):
             if req is not None and actual != req:
-                req_shape = str(shape).replace('None, ', '*, ')
-                msg = '{0} is required to have shape {1} but has shape {2}'
+                req_shape = str(shape).replace("None, ", "*, ")
+                msg = "{0} is required to have shape {1} but has shape {2}"
                 raise ValueError(msg.format(name, req_shape, arr.shape))
     if contiguous:
         arr = np.ascontiguousarray(arr, dtype=dtype)
@@ -193,32 +202,34 @@ def wrap(self, obj, columns=None, append=None, trim_start=0, trim_end=0):
             return obj
 
         if obj.shape[0] + trim_start + trim_end != self._pandas_obj.shape[0]:
-            raise ValueError('obj must have the same number of elements in '
-                             'axis 0 as orig')
+            raise ValueError(
+                "obj must have the same number of elements in "
+                "axis 0 as orig"
+            )
         index = self._pandas_obj.index
-        index = index[trim_start:index.shape[0] - trim_end]
+        index = index[trim_start: index.shape[0] - trim_end]
         if obj.ndim == 1:
             if columns is None:
-                name = getattr(self._pandas_obj, 'name', None)
+                name = getattr(self._pandas_obj, "name", None)
             elif isinstance(columns, str):
                 name = columns
             else:
                 name = columns[0]
             if append is not None:
-                name = append if name is None else name + '_' + append
+                name = append if name is None else f"{name}_{append}"
 
             return pd.Series(obj, name=name, index=index)
         elif obj.ndim == 2:
             if columns is None:
-                columns = getattr(self._pandas_obj, 'columns', None)
+                columns = getattr(self._pandas_obj, "columns", None)
             if append is not None:
                 new = []
                 for c in columns:
-                    new.append(append if c is None else str(c) + '_' + append)
+                    new.append(append if c is None else f"{c}_{append}")
                 columns = new
             return pd.DataFrame(obj, columns=columns, index=index)
         else:
-            raise ValueError('Can only wrap 1 or 2-d array_like')
+            raise ValueError("Can only wrap 1 or 2-d array_like")
 
 
 def bool_like(value, name, optional=False, strict=False):
@@ -244,24 +255,26 @@ def bool_like(value, name, optional=False, strict=False):
     """
     if optional and value is None:
         return value
-    extra_text = ' or None' if optional else ''
+    extra_text = " or None" if optional else ""
     if strict:
         if isinstance(value, bool):
             return value
         else:
-            raise TypeError('{0} must be a bool{1}'.format(name, extra_text))
+            raise TypeError("{0} must be a bool{1}".format(name, extra_text))
 
-    if hasattr(value, 'squeeze') and callable(value.squeeze):
+    if hasattr(value, "squeeze") and callable(value.squeeze):
         value = value.squeeze()
     try:
         return bool(value)
     except Exception:
-        raise TypeError('{0} must be a bool (or bool-compatible)'
-                        '{1}'.format(name, extra_text))
+        raise TypeError(
+            "{0} must be a bool (or bool-compatible)"
+            "{1}".format(name, extra_text)
+        )
 
 
 def int_like(
-        value: Any, name: str, optional: bool = False, strict: bool = False
+    value: Any, name: str, optional: bool = False, strict: bool = False
 ) -> Optional[int]:
     """
     Convert to int or raise if not int_like
@@ -287,7 +300,7 @@ def int_like(
         return None
     is_bool_timedelta = isinstance(value, (bool, np.timedelta64))
 
-    if hasattr(value, 'squeeze') and callable(value.squeeze):
+    if hasattr(value, "squeeze") and callable(value.squeeze):
         value = value.squeeze()
 
     if isinstance(value, (int, np.integer)) and not is_bool_timedelta:
@@ -298,9 +311,11 @@ def int_like(
                 return int(value)
         except Exception:
             pass
-    extra_text = ' or None' if optional else ''
-    raise TypeError('{0} must be integer_like (int or np.integer, but not bool'
-                    ' or timedelta64){1}'.format(name, extra_text))
+    extra_text = " or None" if optional else ""
+    raise TypeError(
+        "{0} must be integer_like (int or np.integer, but not bool"
+        " or timedelta64){1}".format(name, extra_text)
+    )
 
 
 def required_int_like(value: Any, name: str, strict: bool = False) -> int:
@@ -356,11 +371,12 @@ def float_like(value, name, optional=False, strict=False):
         return None
     is_bool = isinstance(value, bool)
     is_complex = isinstance(value, (complex, np.complexfloating))
-    if hasattr(value, 'squeeze') and callable(value.squeeze):
+    if hasattr(value, "squeeze") and callable(value.squeeze):
         value = value.squeeze()
 
-    if (isinstance(value, (int, np.integer, float, np.inexact)) and
-            not (is_bool or is_complex)):
+    if isinstance(value, (int, np.integer, float, np.inexact)) and not (
+        is_bool or is_complex
+    ):
         return float(value)
     elif not strict and is_complex:
         imag = np.imag(value)
@@ -371,24 +387,27 @@ def float_like(value, name, optional=False, strict=False):
             return float(value / 1.0)
         except Exception:
             pass
-    extra_text = ' or None' if optional else ''
-    raise TypeError('{0} must be float_like (float or np.inexact)'
-                    '{1}'.format(name, extra_text))
+    extra_text = " or None" if optional else ""
+    raise TypeError(
+        "{0} must be float_like (float or np.inexact)"
+        "{1}".format(name, extra_text)
+    )
 
 
 def string_like(value, name, optional=False, options=None, lower=True):
     if value is None:
         return None
     if not isinstance(value, str):
-        extra_text = ' or None' if optional else ''
-        raise TypeError('{0} must be a string{1}'.format(name, extra_text))
+        extra_text = " or None" if optional else ""
+        raise TypeError("{0} must be a string{1}".format(name, extra_text))
     if lower:
         value = value.lower()
     if options is not None and value not in options:
-        extra_text = 'If not None, ' if optional else ''
-        options_text = "'" + '\', \''.join(options) + "'"
-        msg = '{0}{1} must be one of: {2}'.format(extra_text,
-                                                  name, options_text)
+        extra_text = "If not None, " if optional else ""
+        options_text = "'" + "', '".join(options) + "'"
+        msg = "{0}{1} must be one of: {2}".format(
+            extra_text, name, options_text
+        )
         raise ValueError(msg)
     return value
 
@@ -415,10 +434,11 @@ def dict_like(value, name, optional=False, strict=True):
     """
     if optional and value is None:
         return None
-    if (not isinstance(value, Mapping) or
-            (strict and not(isinstance(value, dict)))):
-        extra_text = 'If not None, ' if optional else ''
-        strict_text = ' or dict_like (i.e., a Mapping)' if strict else ''
-        msg = '{0}{1} must be a dict{2}'.format(extra_text, name, strict_text)
+    if not isinstance(value, Mapping) or (
+        strict and not (isinstance(value, dict))
+    ):
+        extra_text = "If not None, " if optional else ""
+        strict_text = " or dict_like (i.e., a Mapping)" if strict else ""
+        msg = "{0}{1} must be a dict{2}".format(extra_text, name, strict_text)
         raise TypeError(msg)
     return value


Test patch:
diff --git a/statsmodels/tools/validation/tests/test_validation.py b/statsmodels/tools/validation/tests/test_validation.py
--- a/statsmodels/tools/validation/tests/test_validation.py
+++ b/statsmodels/tools/validation/tests/test_validation.py
@@ -4,9 +4,15 @@
 import pandas as pd
 import pytest
 
-from statsmodels.tools.validation import (array_like, PandasWrapper, bool_like,
-                                          dict_like, float_like, int_like,
-                                          string_like)
+from statsmodels.tools.validation import (
+    array_like,
+    PandasWrapper,
+    bool_like,
+    dict_like,
+    float_like,
+    int_like,
+    string_like,
+)
 
 from statsmodels.tools.validation.validation import _right_squeeze
 
@@ -18,7 +24,7 @@ def use_pandas(request):
 
 def gen_data(dim, use_pandas):
     if dim == 1:
-        out = np.empty(10, )
+        out = np.empty(10,)
         if use_pandas:
             out = pd.Series(out)
     elif dim == 2:
@@ -34,130 +40,130 @@ def gen_data(dim, use_pandas):
 class TestArrayLike(object):
     def test_1d(self, use_pandas):
         data = gen_data(1, use_pandas)
-        a = array_like(data, 'a')
+        a = array_like(data, "a")
         assert a.ndim == 1
         assert a.shape == (10,)
         assert type(a) is np.ndarray
 
-        a = array_like(data, 'a', ndim=1)
+        a = array_like(data, "a", ndim=1)
         assert a.ndim == 1
-        a = array_like(data, 'a', shape=(10,))
+        a = array_like(data, "a", shape=(10,))
         assert a.shape == (10,)
-        a = array_like(data, 'a', ndim=1, shape=(None,))
+        a = array_like(data, "a", ndim=1, shape=(None,))
         assert a.ndim == 1
-        a = array_like(data, 'a', ndim=2, shape=(10, 1))
+        a = array_like(data, "a", ndim=2, shape=(10, 1))
         assert a.ndim == 2
         assert a.shape == (10, 1)
 
-        with pytest.raises(ValueError, match='a is required to have shape'):
-            array_like(data, 'a', shape=(5,))
+        with pytest.raises(ValueError, match="a is required to have shape"):
+            array_like(data, "a", shape=(5,))
 
     def test_2d(self, use_pandas):
         data = gen_data(2, use_pandas)
-        a = array_like(data, 'a', ndim=2)
+        a = array_like(data, "a", ndim=2)
         assert a.ndim == 2
         assert a.shape == (20, 10)
         assert type(a) is np.ndarray
 
-        a = array_like(data, 'a', ndim=2)
+        a = array_like(data, "a", ndim=2)
         assert a.ndim == 2
-        a = array_like(data, 'a', ndim=2, shape=(20, None))
+        a = array_like(data, "a", ndim=2, shape=(20, None))
         assert a.shape == (20, 10)
-        a = array_like(data, 'a', ndim=2, shape=(20,))
+        a = array_like(data, "a", ndim=2, shape=(20,))
         assert a.shape == (20, 10)
-        a = array_like(data, 'a', ndim=2, shape=(None, 10))
+        a = array_like(data, "a", ndim=2, shape=(None, 10))
         assert a.shape == (20, 10)
 
-        a = array_like(data, 'a', ndim=2, shape=(None, None))
+        a = array_like(data, "a", ndim=2, shape=(None, None))
         assert a.ndim == 2
-        a = array_like(data, 'a', ndim=3)
+        a = array_like(data, "a", ndim=3)
         assert a.ndim == 3
         assert a.shape == (20, 10, 1)
 
-        with pytest.raises(ValueError, match='a is required to have shape'):
-            array_like(data, 'a', ndim=2, shape=(10,))
-        with pytest.raises(ValueError, match='a is required to have shape'):
-            array_like(data, 'a', ndim=2, shape=(20, 20))
-        with pytest.raises(ValueError, match='a is required to have shape'):
-            array_like(data, 'a', ndim=2, shape=(None, 20))
-        match = 'a is required to have ndim 1 but has ndim 2'
+        with pytest.raises(ValueError, match="a is required to have shape"):
+            array_like(data, "a", ndim=2, shape=(10,))
+        with pytest.raises(ValueError, match="a is required to have shape"):
+            array_like(data, "a", ndim=2, shape=(20, 20))
+        with pytest.raises(ValueError, match="a is required to have shape"):
+            array_like(data, "a", ndim=2, shape=(None, 20))
+        match = "a is required to have ndim 1 but has ndim 2"
         with pytest.raises(ValueError, match=match):
-            array_like(data, 'a', ndim=1)
-        match = 'a must have ndim <= 1'
+            array_like(data, "a", ndim=1)
+        match = "a must have ndim <= 1"
         with pytest.raises(ValueError, match=match):
-            array_like(data, 'a', maxdim=1)
+            array_like(data, "a", maxdim=1)
 
     def test_3d(self):
         data = gen_data(3, False)
-        a = array_like(data, 'a', ndim=3)
+        a = array_like(data, "a", ndim=3)
         assert a.shape == (5, 6, 7)
         assert a.ndim == 3
         assert type(a) is np.ndarray
 
-        a = array_like(data, 'a', ndim=3, shape=(5, None, 7))
+        a = array_like(data, "a", ndim=3, shape=(5, None, 7))
         assert a.shape == (5, 6, 7)
-        a = array_like(data, 'a', ndim=3, shape=(None, None, 7))
+        a = array_like(data, "a", ndim=3, shape=(None, None, 7))
         assert a.shape == (5, 6, 7)
-        a = array_like(data, 'a', ndim=5)
+        a = array_like(data, "a", ndim=5)
         assert a.shape == (5, 6, 7, 1, 1)
-        with pytest.raises(ValueError, match='a is required to have shape'):
-            array_like(data, 'a', ndim=3, shape=(10,))
-        with pytest.raises(ValueError, match='a is required to have shape'):
-            array_like(data, 'a', ndim=3, shape=(None, None, 5))
-        match = 'a is required to have ndim 2 but has ndim 3'
+        with pytest.raises(ValueError, match="a is required to have shape"):
+            array_like(data, "a", ndim=3, shape=(10,))
+        with pytest.raises(ValueError, match="a is required to have shape"):
+            array_like(data, "a", ndim=3, shape=(None, None, 5))
+        match = "a is required to have ndim 2 but has ndim 3"
         with pytest.raises(ValueError, match=match):
-            array_like(data, 'a', ndim=2)
-        match = 'a must have ndim <= 1'
+            array_like(data, "a", ndim=2)
+        match = "a must have ndim <= 1"
         with pytest.raises(ValueError, match=match):
-            array_like(data, 'a', maxdim=1)
-        match = 'a must have ndim <= 2'
+            array_like(data, "a", maxdim=1)
+        match = "a must have ndim <= 2"
         with pytest.raises(ValueError, match=match):
-            array_like(data, 'a', maxdim=2)
+            array_like(data, "a", maxdim=2)
 
     def test_right_squeeze_and_pad(self):
         data = np.empty((2, 1, 2))
-        a = array_like(data, 'a', ndim=3)
+        a = array_like(data, "a", ndim=3)
         assert a.shape == (2, 1, 2)
         data = np.empty((2))
-        a = array_like(data, 'a', ndim=3)
+        a = array_like(data, "a", ndim=3)
         assert a.shape == (2, 1, 1)
         data = np.empty((2, 1))
-        a = array_like(data, 'a', ndim=3)
+        a = array_like(data, "a", ndim=3)
         assert a.shape == (2, 1, 1)
 
         data = np.empty((2, 1, 1, 1))
-        a = array_like(data, 'a', ndim=3)
+        a = array_like(data, "a", ndim=3)
         assert a.shape == (2, 1, 1)
 
         data = np.empty((2, 1, 1, 2, 1, 1))
         with pytest.raises(ValueError):
-            array_like(data, 'a', ndim=3)
+            array_like(data, "a", ndim=3)
 
     def test_contiguous(self):
         x = np.arange(10)
         y = x[::2]
-        a = array_like(y, 'a', contiguous=True)
-        assert not y.flags['C_CONTIGUOUS']
-        assert a.flags['C_CONTIGUOUS']
+        a = array_like(y, "a", contiguous=True)
+        assert not y.flags["C_CONTIGUOUS"]
+        assert a.flags["C_CONTIGUOUS"]
 
     def test_dtype(self):
         x = np.arange(10)
-        a = array_like(x, 'a', dtype=np.float32)
+        a = array_like(x, "a", dtype=np.float32)
         assert a.dtype == np.float32
 
-        a = array_like(x, 'a', dtype=np.uint8)
+        a = array_like(x, "a", dtype=np.uint8)
         assert a.dtype == np.uint8
 
-    @pytest.mark.xfail(reason='Failing for now')
+    @pytest.mark.xfail(reason="Failing for now")
     def test_dot(self, use_pandas):
         data = gen_data(2, use_pandas)
-        a = array_like(data, 'a')
+        a = array_like(data, "a")
         assert not isinstance(a.T.dot(data), array_like)
         assert not isinstance(a.T.dot(a), array_like)
 
     def test_slice(self, use_pandas):
         data = gen_data(2, use_pandas)
-        a = array_like(data, 'a', ndim=2)
+        a = array_like(data, "a", ndim=2)
         assert type(a[1:]) is np.ndarray
 
 
@@ -188,46 +194,63 @@ def test_wrap_pandas(use_pandas):
     assert isinstance(wrapped, expected_type)
     assert not use_pandas or wrapped.name is None
 
-    wrapped = PandasWrapper(a).wrap(b, columns='name')
+    wrapped = PandasWrapper(a).wrap(b, columns="name")
     assert isinstance(wrapped, expected_type)
-    assert not use_pandas or wrapped.name == 'name'
+    assert not use_pandas or wrapped.name == "name"
 
-    wrapped = PandasWrapper(a).wrap(b, columns=['name'])
+    wrapped = PandasWrapper(a).wrap(b, columns=["name"])
     assert isinstance(wrapped, expected_type)
-    assert not use_pandas or wrapped.name == 'name'
+    assert not use_pandas or wrapped.name == "name"
 
     expected_type = pd.DataFrame if use_pandas else np.ndarray
     wrapped = PandasWrapper(a).wrap(b[:, None])
     assert isinstance(wrapped, expected_type)
     assert not use_pandas or wrapped.columns[0] == 0
 
-    wrapped = PandasWrapper(a).wrap(b[:, None], columns=['name'])
+    wrapped = PandasWrapper(a).wrap(b[:, None], columns=["name"])
     assert isinstance(wrapped, expected_type)
-    assert not use_pandas or wrapped.columns == ['name']
+    assert not use_pandas or wrapped.columns == ["name"]
 
     if use_pandas:
-        match = 'Can only wrap 1 or 2-d array_like'
+        match = "Can only wrap 1 or 2-d array_like"
         with pytest.raises(ValueError, match=match):
             PandasWrapper(a).wrap(b[:, None, None])
 
-        match = 'obj must have the same number of elements in axis 0 as'
+        match = "obj must have the same number of elements in axis 0 as"
         with pytest.raises(ValueError, match=match):
-            PandasWrapper(a).wrap(b[:b.shape[0] // 2])
+            PandasWrapper(a).wrap(b[: b.shape[0] // 2])
 
 
 def test_wrap_pandas_append():
     a = gen_data(1, True)
-    a.name = 'apple'
+    a.name = "apple"
     b = gen_data(1, False)
-    wrapped = PandasWrapper(a).wrap(b, append='appended')
-    expected = 'apple_appended'
+    wrapped = PandasWrapper(a).wrap(b, append="appended")
+    expected = "apple_appended"
     assert wrapped.name == expected
 
     a = gen_data(2, True)
-    a.columns = ['apple_' + str(i) for i in range(a.shape[1])]
+    a.columns = ["apple_" + str(i) for i in range(a.shape[1])]
     b = gen_data(2, False)
-    wrapped = PandasWrapper(a).wrap(b, append='appended')
-    expected = [c + '_appended' for c in a.columns]
+    wrapped = PandasWrapper(a).wrap(b, append="appended")
+    expected = [c + "_appended" for c in a.columns]
+    assert list(wrapped.columns) == expected
+
+
+def test_wrap_pandas_append_non_string():
+    # GH 6826
+    a = gen_data(1, True)
+    a.name = 7
+    b = gen_data(1, False)
+    wrapped = PandasWrapper(a).wrap(b, append="appended")
+    expected = "7_appended"
+    assert wrapped.name == expected
+
+    a = gen_data(2, True)
+    a.columns = [i for i in range(a.shape[1])]
+    b = gen_data(2, False)
+    wrapped = PandasWrapper(a).wrap(b, append="appended")
+    expected = [f"{c}_appended" for c in a.columns]
     assert list(wrapped.columns) == expected
 
 
@@ -242,121 +265,130 @@ def dict_type(request):
 
 def test_optional_dict_like(dict_type):
     val = dict_type() if dict_type is not None else dict_type
-    out = dict_like(val, 'value', optional=True)
+    out = dict_like(val, "value", optional=True)
     assert isinstance(out, type(val))
 
 
 def test_optional_dict_like_error():
-    match = r'value must be a dict or dict_like \(i.e., a Mapping\)'
+    match = r"value must be a dict or dict_like \(i.e., a Mapping\)"
     with pytest.raises(TypeError, match=match):
-        dict_like([], 'value', optional=True)
+        dict_like([], "value", optional=True)
     with pytest.raises(TypeError, match=match):
-        dict_like({'a'}, 'value', optional=True)
+        dict_like({"a"}, "value", optional=True)
     with pytest.raises(TypeError, match=match):
-        dict_like('a', 'value', optional=True)
+        dict_like("a", "value", optional=True)
 
 
 def test_string():
-    out = string_like('apple', 'value')
-    assert out == 'apple'
+    out = string_like("apple", "value")
+    assert out == "apple"
 
-    out = string_like('apple', 'value', options=('apple', 'banana', 'cherry'))
-    assert out == 'apple'
+    out = string_like("apple", "value", options=("apple", "banana", "cherry"))
+    assert out == "apple"
 
-    with pytest.raises(TypeError, match='value must be a string'):
-        string_like(1, 'value')
-    with pytest.raises(TypeError, match='value must be a string'):
-        string_like(b'4', 'value')
-    with pytest.raises(ValueError, match='value must be one of: \'apple\','
-                                         ' \'banana\', \'cherry\''):
-        string_like('date', 'value',
-                            options=('apple', 'banana', 'cherry'))
+    with pytest.raises(TypeError, match="value must be a string"):
+        string_like(1, "value")
+    with pytest.raises(TypeError, match="value must be a string"):
+        string_like(b"4", "value")
+    with pytest.raises(
+        ValueError,
+        match="value must be one of: 'apple'," " 'banana', 'cherry'",
+    ):
+        string_like("date", "value", options=("apple", "banana", "cherry"))
 
 
 def test_optional_string():
-    out = string_like('apple', 'value')
-    assert out == 'apple'
+    out = string_like("apple", "value")
+    assert out == "apple"
 
-    out = string_like('apple', 'value', options=('apple', 'banana', 'cherry'))
-    assert out == 'apple'
+    out = string_like("apple", "value", options=("apple", "banana", "cherry"))
+    assert out == "apple"
 
-    out = string_like(None, 'value', optional=True)
+    out = string_like(None, "value", optional=True)
     assert out is None
 
-    out = string_like(None, 'value', optional=True,
-                      options=('apple', 'banana', 'cherry'))
+    out = string_like(
+        None, "value", optional=True, options=("apple", "banana", "cherry")
+    )
     assert out is None
 
-    with pytest.raises(TypeError, match='value must be a string'):
-        string_like(1, 'value', optional=True)
-    with pytest.raises(TypeError, match='value must be a string'):
-        string_like(b'4', 'value', optional=True)
+    with pytest.raises(TypeError, match="value must be a string"):
+        string_like(1, "value", optional=True)
+    with pytest.raises(TypeError, match="value must be a string"):
+        string_like(b"4", "value", optional=True)
 
 
-@pytest.fixture(params=(1., 1.1, np.float32(1.2), np.array([1.2]), 1.2 + 0j))
+@pytest.fixture(params=(1.0, 1.1, np.float32(1.2), np.array([1.2]), 1.2 + 0j))
 def floating(request):
     return request.param
 
 
-@pytest.fixture(params=(np.empty(2), 1.2 + 1j, True, '3.2', None))
+@pytest.fixture(params=(np.empty(2), 1.2 + 1j, True, "3.2", None))
 def not_floating(request):
     return request.param
 
 
 def test_float_like(floating):
-    assert isinstance(float_like(floating, 'floating'), float)
-    assert isinstance(float_like(floating, 'floating', optional=True), float)
-    assert float_like(None, 'floating', optional=True) is None
+    assert isinstance(float_like(floating, "floating"), float)
+    assert isinstance(float_like(floating, "floating", optional=True), float)
+    assert float_like(None, "floating", optional=True) is None
     if isinstance(floating, (int, np.integer, float, np.inexact)):
-        assert isinstance(float_like(floating, 'floating', strict=True), float)
-        assert float_like(None, 'floating', optional=True,
-                          strict=True) is None
+        assert isinstance(float_like(floating, "floating", strict=True), float)
+        assert float_like(None, "floating", optional=True, strict=True) is None
 
 
 def test_not_float_like(not_floating):
     with pytest.raises(TypeError):
-        float_like(not_floating, 'floating')
+        float_like(not_floating, "floating")
 
 
-@pytest.fixture(params=(1., 2, np.float32(3.0), np.array([4.0])))
+@pytest.fixture(params=(1.0, 2, np.float32(3.0), np.array([4.0])))
 def integer(request):
     return request.param
 
 
-@pytest.fixture(params=(3.2, np.float32(3.2), 3 + 2j, complex(2.3 + 0j),
-                        'apple', 1.0 + 0j, np.timedelta64(2)))
+@pytest.fixture(
+    params=(
+        3.2,
+        np.float32(3.2),
+        3 + 2j,
+        np.complex(2.3 + 0j),
+        "apple",
+        1.0 + 0j,
+        np.timedelta64(2),
+    )
+)
 def not_integer(request):
     return request.param
 
 
 def test_int_like(integer):
-    assert isinstance(int_like(integer, 'integer'), int)
-    assert isinstance(int_like(integer, 'integer', optional=True), int)
-    assert int_like(None, 'floating', optional=True) is None
+    assert isinstance(int_like(integer, "integer"), int)
+    assert isinstance(int_like(integer, "integer", optional=True), int)
+    assert int_like(None, "floating", optional=True) is None
     if isinstance(integer, (int, np.integer)):
-        assert isinstance(int_like(integer, 'integer', strict=True), int)
-        assert int_like(None, 'floating', optional=True, strict=True) is None
+        assert isinstance(int_like(integer, "integer", strict=True), int)
+        assert int_like(None, "floating", optional=True, strict=True) is None
 
 
 def test_not_int_like(not_integer):
     with pytest.raises(TypeError):
-        int_like(not_integer, 'integer')
+        int_like(not_integer, "integer")
 
 
-@pytest.fixture(params=[True, False, 1, 1.2, 'a', ''])
+@pytest.fixture(params=[True, False, 1, 1.2, "a", ""])
 def boolean(request):
     return request.param
 
 
 def test_bool_like(boolean):
-    assert isinstance(bool_like(boolean, 'boolean'), bool)
-    assert bool_like(None, 'boolean', optional=True) is None
+    assert isinstance(bool_like(boolean, "boolean"), bool)
+    assert bool_like(None, "boolean", optional=True) is None
     if isinstance(boolean, bool):
-        assert isinstance(bool_like(boolean, 'boolean', strict=True),
-                          bool)
+        assert isinstance(bool_like(boolean, "boolean", strict=True), bool)
     else:
         with pytest.raises(TypeError):
-            bool_like(boolean, 'boolean', strict=True)
+            bool_like(boolean, "boolean", strict=True)
 
 
 def test_not_bool_like():