diff --git a/ci/azure/posix.yml b/ci/azure/posix.yml index 62b15bae6d2ce2..d6afb263b447fb 100644 --- a/ci/azure/posix.yml +++ b/ci/azure/posix.yml @@ -9,17 +9,16 @@ jobs: strategy: matrix: ${{ if eq(parameters.name, 'macOS') }}: - py35_macos: - ENV_FILE: ci/deps/azure-macos-35.yaml - CONDA_PY: "35" + py36_macos: + ENV_FILE: ci/deps/azure-macos-36.yaml + CONDA_PY: "36" PATTERN: "not slow and not network" ${{ if eq(parameters.name, 'Linux') }}: - py35_compat: - ENV_FILE: ci/deps/azure-35-compat.yaml - CONDA_PY: "35" + py36_minimum_versions: + ENV_FILE: ci/deps/azure-36-minimum_versions.yaml + CONDA_PY: "36" PATTERN: "not slow and not network" - py36_locale_slow_old_np: ENV_FILE: ci/deps/azure-36-locale.yaml CONDA_PY: "36" diff --git a/ci/deps/azure-35-compat.yaml b/ci/deps/azure-36-minimum_versions.yaml similarity index 69% rename from ci/deps/azure-35-compat.yaml rename to ci/deps/azure-36-minimum_versions.yaml index dd54001984ec71..e2c78165fe4b9f 100644 --- a/ci/deps/azure-35-compat.yaml +++ b/ci/deps/azure-36-minimum_versions.yaml @@ -5,26 +5,23 @@ channels: dependencies: - beautifulsoup4=4.6.0 - bottleneck=1.2.1 + - cython>=0.29.13 - jinja2=2.8 - numexpr=2.6.2 - numpy=1.13.3 - openpyxl=2.4.8 - pytables=3.4.2 - python-dateutil=2.6.1 - - python=3.5.3 + - python=3.6.1 - pytz=2017.2 - scipy=0.19.0 - xlrd=1.1.0 - xlsxwriter=0.9.8 - xlwt=1.2.0 # universal + - html5lib=1.0.1 - hypothesis>=3.58.0 + - pytest=4.5.0 - pytest-xdist - pytest-mock - pytest-azurepipelines - - pip - - pip: - # for python 3.5, pytest>=4.0.2, cython>=0.29.13 is not available in conda - - cython>=0.29.13 - - pytest==4.5.0 - - html5lib==1.0b2 diff --git a/ci/deps/azure-macos-35.yaml b/ci/deps/azure-macos-36.yaml similarity index 97% rename from ci/deps/azure-macos-35.yaml rename to ci/deps/azure-macos-36.yaml index 4e0f09904b6953..85c090bf6f9384 100644 --- a/ci/deps/azure-macos-35.yaml +++ b/ci/deps/azure-macos-36.yaml @@ -14,7 +14,7 @@ dependencies: - openpyxl - pyarrow - pytables - - python=3.5.* + - python=3.6.* - python-dateutil==2.6.1 - pytz - xarray diff --git a/doc/source/development/contributing.rst b/doc/source/development/contributing.rst index eed4a7862cc5fa..8fe5b174c77d38 100644 --- a/doc/source/development/contributing.rst +++ b/doc/source/development/contributing.rst @@ -236,7 +236,7 @@ Creating a Python environment (pip) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you aren't using conda for your development environment, follow these instructions. -You'll need to have at least python3.5 installed on your system. +You'll need to have at least Python 3.6.1 installed on your system. **Unix**/**Mac OS** @@ -847,29 +847,6 @@ The limitation here is that while a human can reasonably understand that ``is_nu With custom types and inference this is not always possible so exceptions are made, but every effort should be exhausted to avoid ``cast`` before going down such paths. -Syntax Requirements -~~~~~~~~~~~~~~~~~~~ - -Because *pandas* still supports Python 3.5, :pep:`526` does not apply and variables **must** be annotated with type comments. Specifically, this is a valid annotation within pandas: - -.. code-block:: python - - primes = [] # type: List[int] - -Whereas this is **NOT** allowed: - -.. code-block:: python - - primes: List[int] = [] # not supported in Python 3.5! - -Note that function signatures can always be annotated per :pep:`3107`: - -.. code-block:: python - - def sum_of_primes(primes: List[int] = []) -> int: - ... - - Pandas-specific Types ~~~~~~~~~~~~~~~~~~~~~ @@ -1296,7 +1273,7 @@ environment by:: or, to use a specific Python interpreter,:: - asv run -e -E existing:python3.5 + asv run -e -E existing:python3.6 This will display stderr from the benchmarks, and use your local ``python`` that comes from your ``$PATH``. diff --git a/doc/source/development/policies.rst b/doc/source/development/policies.rst index 2083a30af09c34..224948738341ef 100644 --- a/doc/source/development/policies.rst +++ b/doc/source/development/policies.rst @@ -51,7 +51,7 @@ Pandas may change the behavior of experimental features at any time. Python Support ~~~~~~~~~~~~~~ -Pandas will only drop support for specific Python versions (e.g. 3.5.x, 3.6.x) in +Pandas will only drop support for specific Python versions (e.g. 3.6.x, 3.7.x) in pandas **major** releases. .. _SemVer: https://semver.org diff --git a/doc/source/getting_started/dsintro.rst b/doc/source/getting_started/dsintro.rst index 9e18951fe3f4c2..a07fcbd8b67c47 100644 --- a/doc/source/getting_started/dsintro.rst +++ b/doc/source/getting_started/dsintro.rst @@ -564,53 +564,6 @@ to a column created earlier in the same :meth:`~DataFrame.assign`. In the second expression, ``x['C']`` will refer to the newly created column, that's equal to ``dfa['A'] + dfa['B']``. -To write code compatible with all versions of Python, split the assignment in two. - -.. ipython:: python - - dependent = pd.DataFrame({"A": [1, 1, 1]}) - (dependent.assign(A=lambda x: x['A'] + 1) - .assign(B=lambda x: x['A'] + 2)) - -.. warning:: - - Dependent assignment may subtly change the behavior of your code between - Python 3.6 and older versions of Python. - - If you wish to write code that supports versions of python before and after 3.6, - you'll need to take care when passing ``assign`` expressions that - - * Update an existing column - * Refer to the newly updated column in the same ``assign`` - - For example, we'll update column "A" and then refer to it when creating "B". - - .. code-block:: python - - >>> dependent = pd.DataFrame({"A": [1, 1, 1]}) - >>> dependent.assign(A=lambda x: x["A"] + 1, B=lambda x: x["A"] + 2) - - For Python 3.5 and earlier the expression creating ``B`` refers to the - "old" value of ``A``, ``[1, 1, 1]``. The output is then - - .. code-block:: console - - A B - 0 2 3 - 1 2 3 - 2 2 3 - - For Python 3.6 and later, the expression creating ``A`` refers to the - "new" value of ``A``, ``[2, 2, 2]``, which results in - - .. code-block:: console - - A B - 0 2 4 - 1 2 4 - 2 2 4 - - Indexing / selection ~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/source/getting_started/install.rst b/doc/source/getting_started/install.rst index 7d1150c2f65fa8..f5bb152d6c1b91 100644 --- a/doc/source/getting_started/install.rst +++ b/doc/source/getting_started/install.rst @@ -18,7 +18,7 @@ Instructions for installing from source, Python version support ---------------------- -Officially Python 3.5.3 and above, 3.6, 3.7, and 3.8. +Officially Python 3.6.1 and above, 3.7, and 3.8. Installing pandas ----------------- @@ -140,7 +140,7 @@ Installing with ActivePython Installation instructions for `ActivePython `__ can be found `here `__. Versions -2.7 and 3.5 include pandas. +2.7, 3.5 and 3.6 include pandas. Installing using your Linux distribution's package manager. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/source/whatsnew/v1.0.0.rst b/doc/source/whatsnew/v1.0.0.rst index 0f225c258cc6a9..664fcc91bacc4d 100644 --- a/doc/source/whatsnew/v1.0.0.rst +++ b/doc/source/whatsnew/v1.0.0.rst @@ -3,6 +3,10 @@ What's new in 1.0.0 (??) ------------------------ +.. warning:: + + Starting with the 1.x series of releases, pandas only supports Python 3.6.1 and higher. + New Deprecation Policy ~~~~~~~~~~~~~~~~~~~~~~ @@ -37,10 +41,6 @@ See :ref:`policies.version` for more. .. _2019 Pandas User Survey: http://dev.pandas.io/pandas-blog/2019-pandas-user-survey.html .. _SemVer: https://semver.org -.. warning:: - - The minimum supported Python version will be bumped to 3.6 in a future release. - {{ header }} These are the changes in pandas 1.0.0. See :ref:`release` for a full changelog diff --git a/pandas/compat/__init__.py b/pandas/compat/__init__.py index 81431db5b867cc..0419c8ac6057e4 100644 --- a/pandas/compat/__init__.py +++ b/pandas/compat/__init__.py @@ -12,7 +12,6 @@ import sys import warnings -PY35 = sys.version_info[:2] == (3, 5) PY36 = sys.version_info >= (3, 6) PY37 = sys.version_info >= (3, 7) PY38 = sys.version_info >= (3, 8) diff --git a/pandas/io/json/_json.py b/pandas/io/json/_json.py index 6e9e0a0b012001..86153559960316 100644 --- a/pandas/io/json/_json.py +++ b/pandas/io/json/_json.py @@ -12,7 +12,7 @@ from pandas.core.dtypes.common import ensure_str, is_period_dtype -from pandas import DataFrame, MultiIndex, Series, compat, isna, to_datetime +from pandas import DataFrame, MultiIndex, Series, isna, to_datetime from pandas._typing import JSONSerializable from pandas.core.reshape.concat import concat @@ -1115,8 +1115,6 @@ def _parse_no_numpy(self): dtype=None, orient="index", ) - if compat.PY35: - self.obj = self.obj.sort_index(axis="columns").sort_index(axis="index") elif orient == "table": self.obj = parse_table_schema(json, precise_float=self.precise_float) else: diff --git a/pandas/tests/groupby/aggregate/test_aggregate.py b/pandas/tests/groupby/aggregate/test_aggregate.py index b56da5fba6f802..c03ffe317083c8 100644 --- a/pandas/tests/groupby/aggregate/test_aggregate.py +++ b/pandas/tests/groupby/aggregate/test_aggregate.py @@ -598,10 +598,7 @@ def test_agg_with_one_lambda(self): } ) - # sort for 35 and earlier columns = ["height_sqr_min", "height_max", "weight_max"] - if compat.PY35: - columns = ["height_max", "height_sqr_min", "weight_max"] expected = pd.DataFrame( { "height_sqr_min": [82.81, 36.00], @@ -640,7 +637,6 @@ def test_agg_multiple_lambda(self): "weight": [7.9, 7.5, 9.9, 198.0], } ) - # sort for 35 and earlier columns = [ "height_sqr_min", "height_max", @@ -648,14 +644,6 @@ def test_agg_multiple_lambda(self): "height_max_2", "weight_min", ] - if compat.PY35: - columns = [ - "height_max", - "height_max_2", - "height_sqr_min", - "weight_max", - "weight_min", - ] expected = pd.DataFrame( { "height_sqr_min": [82.81, 36.00], diff --git a/pandas/tests/io/json/test_json_table_schema.py b/pandas/tests/io/json/test_json_table_schema.py index 569e2998606140..ef9b0bdf053e9d 100644 --- a/pandas/tests/io/json/test_json_table_schema.py +++ b/pandas/tests/io/json/test_json_table_schema.py @@ -5,8 +5,6 @@ import numpy as np import pytest -from pandas.compat import PY35 - from pandas.core.dtypes.dtypes import CategoricalDtype, DatetimeTZDtype, PeriodDtype import pandas as pd @@ -22,14 +20,6 @@ ) -def assert_results_equal(result, expected): - """Helper function for comparing deserialized JSON with Py35 compat.""" - if PY35: - assert sorted(result.items()) == sorted(expected.items()) - else: - assert result == expected - - class TestBuildSchema: def setup_method(self, method): self.df = DataFrame( @@ -245,7 +235,7 @@ def test_build_series(self): ] ) - assert_results_equal(result, expected) + assert result == expected def test_to_json(self): df = self.df.copy() @@ -335,7 +325,7 @@ def test_to_json(self): ] expected = OrderedDict([("schema", schema), ("data", data)]) - assert_results_equal(result, expected) + assert result == expected def test_to_json_float_index(self): data = pd.Series(1, index=[1.0, 2.0]) @@ -365,7 +355,7 @@ def test_to_json_float_index(self): ] ) - assert_results_equal(result, expected) + assert result == expected def test_to_json_period_index(self): idx = pd.period_range("2016", freq="Q-JAN", periods=2) @@ -386,7 +376,7 @@ def test_to_json_period_index(self): ] expected = OrderedDict([("schema", schema), ("data", data)]) - assert_results_equal(result, expected) + assert result == expected def test_to_json_categorical_index(self): data = pd.Series(1, pd.CategoricalIndex(["a", "b"])) @@ -421,7 +411,7 @@ def test_to_json_categorical_index(self): ] ) - assert_results_equal(result, expected) + assert result == expected def test_date_format_raises(self): with pytest.raises(ValueError): @@ -558,7 +548,7 @@ def test_categorical(self): ] ) - assert_results_equal(result, expected) + assert result == expected @pytest.mark.parametrize( "idx,nm,prop", diff --git a/pandas/tests/io/json/test_pandas.py b/pandas/tests/io/json/test_pandas.py index aa065b6e130798..d31aa04b223e8a 100644 --- a/pandas/tests/io/json/test_pandas.py +++ b/pandas/tests/io/json/test_pandas.py @@ -7,7 +7,7 @@ import numpy as np import pytest -from pandas.compat import PY35, is_platform_32bit, is_platform_windows +from pandas.compat import is_platform_32bit, is_platform_windows import pandas.util._test_decorators as td import pandas as pd @@ -160,9 +160,6 @@ def test_roundtrip_simple(self, orient, convert_axes, numpy, dtype): expected = self.frame.copy() - if not numpy and PY35 and orient in ("index", "columns"): - expected = expected.sort_index() - assert_json_roundtrip_equal(result, expected, orient) @pytest.mark.parametrize("dtype", [False, np.int64]) @@ -174,9 +171,6 @@ def test_roundtrip_intframe(self, orient, convert_axes, numpy, dtype): data, orient=orient, convert_axes=convert_axes, numpy=numpy, dtype=dtype ) expected = self.intframe.copy() - if not numpy and PY35 and orient in ("index", "columns"): - expected = expected.sort_index() - if ( numpy and (is_platform_32bit() or is_platform_windows()) @@ -209,9 +203,6 @@ def test_roundtrip_str_axes(self, orient, convert_axes, numpy, dtype): ) expected = df.copy() - if not numpy and PY35 and orient in ("index", "columns"): - expected = expected.sort_index() - if not dtype: expected = expected.astype(np.int64) @@ -250,7 +241,7 @@ def test_roundtrip_categorical(self, orient, convert_axes, numpy): expected.index = expected.index.astype(str) # Categorical not preserved expected.index.name = None # index names aren't preserved in JSON - if not numpy and (orient == "index" or (PY35 and orient == "columns")): + if not numpy and orient == "index": expected = expected.sort_index() assert_json_roundtrip_equal(result, expected, orient) @@ -317,7 +308,7 @@ def test_roundtrip_mixed(self, orient, convert_axes, numpy): expected = df.copy() expected = expected.assign(**expected.select_dtypes("number").astype(np.int64)) - if not numpy and (orient == "index" or (PY35 and orient == "columns")): + if not numpy and orient == "index": expected = expected.sort_index() assert_json_roundtrip_equal(result, expected, orient) @@ -652,8 +643,6 @@ def test_series_roundtrip_simple(self, orient, numpy): result = pd.read_json(data, typ="series", orient=orient, numpy=numpy) expected = self.series.copy() - if not numpy and PY35 and orient in ("index", "columns"): - expected = expected.sort_index() if orient in ("values", "records"): expected = expected.reset_index(drop=True) if orient != "split": @@ -670,8 +659,6 @@ def test_series_roundtrip_object(self, orient, numpy, dtype): ) expected = self.objSeries.copy() - if not numpy and PY35 and orient in ("index", "columns"): - expected = expected.sort_index() if orient in ("values", "records"): expected = expected.reset_index(drop=True) if orient != "split": @@ -686,8 +673,6 @@ def test_series_roundtrip_empty(self, orient, numpy): expected = self.empty_series.copy() # TODO: see what causes inconsistency - if not numpy and PY35 and orient == "index": - expected = expected.sort_index() if orient in ("values", "records"): expected = expected.reset_index(drop=True) else: @@ -1611,11 +1596,7 @@ def test_json_indent_all_orients(self, orient, expected): # GH 12004 df = pd.DataFrame([["foo", "bar"], ["baz", "qux"]], columns=["a", "b"]) result = df.to_json(orient=orient, indent=4) - - if PY35: - assert json.loads(result) == json.loads(expected) - else: - assert result == expected + assert result == expected def test_json_negative_indent_raises(self): with pytest.raises(ValueError, match="must be a nonnegative integer"): diff --git a/pandas/tests/scalar/period/test_period.py b/pandas/tests/scalar/period/test_period.py index bbd97291fab3f6..3bdf91cbf838b1 100644 --- a/pandas/tests/scalar/period/test_period.py +++ b/pandas/tests/scalar/period/test_period.py @@ -1,5 +1,7 @@ from datetime import date, datetime, timedelta +from distutils.version import StrictVersion +import dateutil import numpy as np import pytest import pytz @@ -10,7 +12,6 @@ from pandas._libs.tslibs.parsing import DateParseError from pandas._libs.tslibs.period import IncompatibleFrequency from pandas._libs.tslibs.timezones import dateutil_gettz, maybe_get_tz -from pandas.compat import PY35 from pandas.compat.numpy import np_datetime64_compat import pandas as pd @@ -1546,10 +1547,8 @@ def test_period_immutable(): @pytest.mark.xfail( - # xpassing on MacPython with strict=False - # https://travis-ci.org/MacPython/pandas-wheels/jobs/574706922 - PY35, - reason="Parsing as Period('0007-01-01', 'D') for reasons unknown", + StrictVersion(dateutil.__version__.split(".dev")[0]) < StrictVersion("2.7.0"), + reason="Bug in dateutil < 2.7.0 when parsing old dates: Period('0001-01-07', 'D')", strict=False, ) def test_small_year_parsing(): diff --git a/pyproject.toml b/pyproject.toml index 2ec4739c2f7f80..b105f8aeb3291b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,10 +5,8 @@ requires = [ "setuptools", "wheel", "Cython>=0.29.13", # Note: sync with setup.py - "numpy==1.13.3; python_version=='3.5' and platform_system!='AIX'", "numpy==1.13.3; python_version=='3.6' and platform_system!='AIX'", "numpy==1.14.5; python_version>='3.7' and platform_system!='AIX'", - "numpy==1.16.0; python_version=='3.5' and platform_system=='AIX'", "numpy==1.16.0; python_version=='3.6' and platform_system=='AIX'", "numpy==1.16.0; python_version>='3.7' and platform_system=='AIX'", ] diff --git a/setup.py b/setup.py index 3dd38bdb6adbbb..a7bc7a333cdd60 100755 --- a/setup.py +++ b/setup.py @@ -223,7 +223,6 @@ def build_extensions(self): "Intended Audience :: Science/Research", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", @@ -812,7 +811,7 @@ def srcpath(name=None, suffix=".pyx", subdir="src"): long_description=LONG_DESCRIPTION, classifiers=CLASSIFIERS, platforms="any", - python_requires=">=3.5.3", + python_requires=">=3.6.1", extras_require={ "test": [ # sync with setup.cfg minversion & install.rst