Repository: alteryx/woodwork (python)

Issue:
Stop using df.append in codebase to resolve warning
- In building the Featuretools docs I saw this warning
```python
/Users/gaurav.sheni/.pyenv/versions/3.9.13/lib/python3.9/site-packages/woodwork/type_sys/utils.py:132: FutureWarning: The frame.append method is deprecated and will be removed from pandas in a future version. Use pandas.concat instead.
```
- We should remove `df.append` and use `df.concat`
- Completion: No more `.append` calls in Woodwork

Pull request description:
Replace uses of dataframe or series `append` with `concat` where appropriate
- Fixes #1493 
- Replaces uses of deprecated `append` method for dataframes and series with `concat` 
- Fixes bug in `test_numeric_histogram` where results of call to `append` were not being used
- Other uses of `append` are not altered

Patch:
diff --git a/docs/source/release_notes.rst b/docs/source/release_notes.rst
index 2186594..125c42b 100644
--- a/docs/source/release_notes.rst
+++ b/docs/source/release_notes.rst
@@ -6,6 +6,7 @@ Release Notes
 Future Release
 ==============
     * Enhancements
+        * Replace use of deprecated ``append`` method for dataframes and series with ``concat`` method (:pr:`1533`)
     * Fixes
         * Fixed bug relating to ``dependence`` calculations to ensure columns exist in dataframe (:pr:`1534`)
     * Changes
@@ -13,7 +14,7 @@ Future Release
     * Testing Changes
 
     Thanks to the following people for contributing to this release:
-    :user:`bchen1116`
+    :user:`bchen1116`, :user:`sbadithe`
 
 v0.19.0 September 27, 2022
 ==========================
diff --git a/woodwork/type_sys/utils.py b/woodwork/type_sys/utils.py
index 20a9919..523d1b9 100644
--- a/woodwork/type_sys/utils.py
+++ b/woodwork/type_sys/utils.py
@@ -129,28 +129,25 @@ def list_semantic_tags():
             for tag in sem_tags
         ],
     )
-    tags_df = tags_df.append(
-        pd.DataFrame(
+    temp_df = pd.DataFrame(
+        [
+            ["index", False, "Any LogicalType"],
+            [
+                "time_index",
+                False,
+                [ww.type_system.str_to_logical_type("datetime")] + sem_tags["numeric"],
+            ],
             [
-                ["index", False, "Any LogicalType"],
-                [
-                    "time_index",
-                    False,
-                    [ww.type_system.str_to_logical_type("datetime")]
-                    + sem_tags["numeric"],
-                ],
-                [
-                    "date_of_birth",
-                    False,
-                    [ww.type_system.str_to_logical_type("datetime")],
-                ],
-                ["ignore", False, "Any LogicalType"],
-                ["passthrough", False, "Any LogicalType"],
+                "date_of_birth",
+                False,
+                [ww.type_system.str_to_logical_type("datetime")],
             ],
-            columns=tags_df.columns,
-        ),
-        ignore_index=True,
+            ["ignore", False, "Any LogicalType"],
+            ["passthrough", False, "Any LogicalType"],
+        ],
+        columns=tags_df.columns,
     )
+    tags_df = pd.concat([tags_df, temp_df], ignore_index=True)
     return tags_df
 
 


Test patch:
diff --git a/woodwork/tests/accessor/test_column_accessor.py b/woodwork/tests/accessor/test_column_accessor.py
index 09b5b64..5b114af 100644
--- a/woodwork/tests/accessor/test_column_accessor.py
+++ b/woodwork/tests/accessor/test_column_accessor.py
@@ -32,7 +32,12 @@ from woodwork.logical_types import (
     PostalCode,
     SubRegionCode,
 )
-from woodwork.tests.testing_utils import is_property, is_public_method, to_pandas
+from woodwork.tests.testing_utils import (
+    concat_dataframe_or_series,
+    is_property,
+    is_public_method,
+    to_pandas,
+)
 from woodwork.utils import import_or_none
 
 dd = import_or_none("dask.dataframe")
@@ -1025,7 +1030,7 @@ def test_validate_logical_type(sample_df):
     if _is_spark_series(series):
         invalid_row = ps.from_pandas(invalid_row)
 
-    series = series.append(invalid_row)
+    series = concat_dataframe_or_series(series, invalid_row)
     series = init_series(series, logical_type="EmailAddress")
     match = "Series email contains invalid email address values. "
     match += "The email_inference_regex can be changed in the config if needed."
diff --git a/woodwork/tests/accessor/test_statistics.py b/woodwork/tests/accessor/test_statistics.py
index 8c3a4d6..cf82805 100644
--- a/woodwork/tests/accessor/test_statistics.py
+++ b/woodwork/tests/accessor/test_statistics.py
@@ -1523,7 +1523,7 @@ def test_datetime_get_recent_value_counts():
 
 def test_numeric_histogram():
     column = pd.Series(np.random.randn(1000))
-    column.append(pd.Series([np.nan, " ", "test"]))
+    column = pd.concat([column, pd.Series([np.nan])])
     bins = 7
     values = _get_histogram_values(column, bins=bins)
     assert len(values) == bins
diff --git a/woodwork/tests/accessor/test_table_accessor.py b/woodwork/tests/accessor/test_table_accessor.py
index 17a7071..319eb5f 100644
--- a/woodwork/tests/accessor/test_table_accessor.py
+++ b/woodwork/tests/accessor/test_table_accessor.py
@@ -64,6 +64,7 @@ from woodwork.table_accessor import (
 )
 from woodwork.table_schema import TableSchema
 from woodwork.tests.testing_utils import (
+    concat_dataframe_or_series,
     is_property,
     is_public_method,
     to_pandas,
@@ -800,7 +801,9 @@ def test_ignore_columns_can_force_logical_type_not_in_ignore_columns():
         "ints": IntegerNullable,
     }
     df.ww.init(
-        ignore_columns={"floats", "dates"}, logical_types=logical_types, schema=schema
+        ignore_columns={"floats", "dates"},
+        logical_types=logical_types,
+        schema=schema,
     )
 
     assert isinstance(df.ww.logical_types["ints"], IntegerNullable)
@@ -3173,7 +3176,7 @@ def test_validate_logical_types(sample_df):
 
     if _is_spark_dataframe(df):
         invalid_df = ps.from_pandas(invalid_df)
-    df = df.append(invalid_df)
+    df = concat_dataframe_or_series(df, invalid_df)
 
     df.ww.init(
         logical_types={
diff --git a/woodwork/tests/logical_types/test_logical_types.py b/woodwork/tests/logical_types/test_logical_types.py
index cc418a5..715271b 100644
--- a/woodwork/tests/logical_types/test_logical_types.py
+++ b/woodwork/tests/logical_types/test_logical_types.py
@@ -30,7 +30,10 @@ from woodwork.logical_types import (
     PostalCode,
     _replace_nans,
 )
-from woodwork.tests.testing_utils.table_utils import to_pandas
+from woodwork.tests.testing_utils.table_utils import (
+    concat_dataframe_or_series,
+    to_pandas,
+)
 from woodwork.utils import import_or_none
 
 ps = import_or_none("pyspark.pandas")
@@ -285,7 +288,8 @@ def test_email_address_validate(sample_df):
 
     assert email_address.validate(series) is None
 
-    series = series.append(invalid_row).astype(dtype)
+    series = concat_dataframe_or_series(series, invalid_row).astype(dtype)
+
     match = "Series email contains invalid email address values. "
     match += "The email_inference_regex can be changed in the config if needed."
 
@@ -307,7 +311,8 @@ def test_url_validate(sample_df):
 
     assert logical_type.validate(series) is None
 
-    series = series.append(invalid_row).astype(dtype)
+    series = concat_dataframe_or_series(series, invalid_row).astype(dtype)
+
     match = "Series url contains invalid url values. "
     match += "The url_inference_regex can be changed in the config if needed."
 
@@ -337,7 +342,8 @@ def test_age_validate(sample_df, logical_type):
 
     if _is_spark_series(series):
         invalid_row = ps.from_pandas(invalid_row)
-    series = series.append(invalid_row).astype(dtype)
+
+    series = concat_dataframe_or_series(series, invalid_row).astype(dtype)
 
     match = "Series age contains negative values."
     with pytest.raises(TypeValidationError, match=match):
@@ -358,7 +364,8 @@ def test_phone_number_validate(sample_df):
 
     assert phone_number.validate(series) is None
 
-    series = series.append(invalid_row).astype(dtype)
+    series = concat_dataframe_or_series(series, invalid_row).astype(dtype)
+
     match = "Series phone_number contains invalid phone number values. "
     match += "The phone_inference_regex can be changed in the config if needed."
 
@@ -380,7 +387,8 @@ def test_phone_number_validate_complex(sample_df_phone_numbers):
         name="phone_number",
     ).astype(dtype)
 
-    series = series.append(invalid_row).astype(dtype)
+    series = concat_dataframe_or_series(series, invalid_row).astype(dtype)
+
     actual = phone_number.validate(series, return_invalid_values=True)
     expected = pd.Series(
         {17: "252 9384", 18: "+1 194 129 1991", 19: "+01 236 248 8482"},
@@ -399,7 +407,9 @@ def test_postal_code_validate(sample_df_postal_code):
             "51342-HEL0",
         ],
     )
-    series = series.append(invalid_types)
+
+    series = concat_dataframe_or_series(series, invalid_types)
+
     series.name = "postal_code"
     match = "Series postal_code contains invalid postal code values. "
     match += "The postal_code_inference_regex can be changed in the config if needed."
@@ -427,7 +437,9 @@ def test_postal_code_validate_complex(sample_df_postal_code):
     )
     actual = pc.validate(series, return_invalid_values=True)
     assert not len(actual)
-    series = series.append(invalid_types)
+
+    series = concat_dataframe_or_series(series, invalid_types)
+
     actual = pc.validate(series, return_invalid_values=True)
     pd.testing.assert_series_equal(actual, invalid_types)
 
@@ -446,7 +458,10 @@ def test_postal_code_validate_numeric(postal_code_numeric_series):
 
 
 def test_postal_code_error(postal_code_numeric_series_pandas):
-    series = postal_code_numeric_series_pandas.append(pd.Series([1234.5]))
+    series = concat_dataframe_or_series(
+        postal_code_numeric_series_pandas,
+        pd.Series([1234.5]),
+    )
     match = (
         "Error converting datatype for None from type float64 to type string. "
         "Please confirm the underlying data is consistent with logical type PostalCode."
diff --git a/woodwork/tests/testing_utils/__init__.py b/woodwork/tests/testing_utils/__init__.py
index 9169996..6e5405c 100644
--- a/woodwork/tests/testing_utils/__init__.py
+++ b/woodwork/tests/testing_utils/__init__.py
@@ -1,8 +1,10 @@
 # flake8: noqa
 import pytest
+
 from woodwork.tests.testing_utils.table_utils import (
     _check_close,
     check_empty_box_plot_dict,
+    concat_dataframe_or_series,
     dep_between_cols,
     is_property,
     is_public_method,
diff --git a/woodwork/tests/testing_utils/table_utils.py b/woodwork/tests/testing_utils/table_utils.py
index 65ff451..1ff1679 100644
--- a/woodwork/tests/testing_utils/table_utils.py
+++ b/woodwork/tests/testing_utils/table_utils.py
@@ -1,6 +1,7 @@
 import numpy as np
 import pandas as pd
 
+from woodwork import accessor_utils
 from woodwork.utils import import_or_none
 
 dd = import_or_none("dask.dataframe")
@@ -96,3 +97,30 @@ def _check_close(actual, expected):
         assert pd.isnull(actual)
     else:
         np.testing.assert_allclose(actual, expected, atol=1e-3)
+
+
+def concat_dataframe_or_series(base, to_add):
+    """Selects and calls the appropriate concat method based on the type of the base and to_add Series/DataFrame
+
+    Args:
+        base: base Series/DataFrame
+        to_add: Series/DataFrame to be concatenated
+
+    Returns:
+        Series/DataFrame: result of concatenation
+    """
+    dd = import_or_none("dask.dataframe")
+    ps = import_or_none("pyspark.pandas")
+
+    if isinstance(base, (pd.Series, pd.DataFrame)):
+        concatenated_obj = pd.concat([base, to_add])
+    elif accessor_utils._is_dask_dataframe(
+        base,
+    ) or accessor_utils._is_dask_series(base):
+        concatenated_obj = dd.concat([base, to_add])
+    elif accessor_utils._is_spark_dataframe(
+        base,
+    ) or accessor_utils._is_spark_series(base):
+        concatenated_obj = ps.concat([base, to_add])
+
+    return concatenated_obj
diff --git a/woodwork/tests/utils/test_utils.py b/woodwork/tests/utils/test_utils.py
index f890a89..ede87dd 100644
--- a/woodwork/tests/utils/test_utils.py
+++ b/woodwork/tests/utils/test_utils.py
@@ -24,6 +24,7 @@ from woodwork.logical_types import (
     PostalCode,
     SubRegionCode,
 )
+from woodwork.tests.testing_utils import concat_dataframe_or_series
 from woodwork.type_sys.type_system import DEFAULT_INFERENCE_FUNCTIONS
 from woodwork.type_sys.utils import (
     _get_specified_ltype_params,
@@ -617,3 +618,53 @@ def test_callback_caller_no_callback():
     caller.update(1)
 
     assert caller.current_progress == 0
+
+
+def test_concat_dataframe_or_series_with_series():
+    """Tests whether series are correctly concatenated"""
+    pandas_series = pd.Series([1, 2, 3])
+
+    assert len(concat_dataframe_or_series(pandas_series, pandas_series)) == 2 * len(
+        pandas_series,
+    )
+
+    if dd:
+        dask_series = dd.from_pandas(pandas_series, npartitions=1)
+        assert len(concat_dataframe_or_series(dask_series, dask_series)) == 2 * len(
+            dask_series,
+        )
+    if ps:
+        spark_series = ps.Series(data=[1, 2, 3])
+        assert len(concat_dataframe_or_series(spark_series, spark_series)) == 2 * len(
+            spark_series,
+        )
+
+
+def test_concat_dataframe_or_series_with_series_with_dataframe():
+    """Tests whether dataframes are correctly concatenated"""
+    d = {"col1": [1, 2], "col2": [3, 4]}
+    df = pd.DataFrame(data=d)
+
+    assert len(concat_dataframe_or_series(df, df)) == 2 * len(
+        df,
+    )
+
+    if dd:
+        dask_df = dd.from_pandas(df, npartitions=1)
+        assert len(concat_dataframe_or_series(dask_df, dask_df)) == 2 * len(
+            dask_df,
+        )
+    if ps:
+        spark_df = ps.from_pandas(df)
+        assert len(concat_dataframe_or_series(spark_df, spark_df)) == 2 * len(
+            spark_df,
+        )
+
+
+def tests_concat_dataframe_or_series_concatenates_in_correct_order():
+    """Tests to_add argument is appropriately added to end of base argument"""
+    base = pd.Series([1, 2, 3])
+    to_add = pd.Series([4, 5, 6])
+    concatenated_object = concat_dataframe_or_series(base, to_add)
+    assert concatenated_object.head(3).equals(pd.Series([1, 2, 3]))
+    assert concatenated_object.tail(3).equals(pd.Series([4, 5, 6]))