diff --git a/.circleci/config.yml b/.circleci/config.yml index ef17bac91d7..18a1d5bbc71 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,7 +7,7 @@ commands: test_core: parameters: py: - default: "36" + default: "310" type: string steps: - checkout @@ -32,7 +32,7 @@ commands: test_optional: parameters: py: - default: "36" + default: "310" type: string steps: - checkout @@ -95,7 +95,7 @@ commands: test_orca: parameters: py: - default: "36" + default: "310" type: string steps: - checkout @@ -153,20 +153,6 @@ jobs: black --check . --exclude venv # Core - python_36_core: - docker: - - image: cimg/python:3.6-browsers - steps: - - test_core: - py: "36" - - python_37_core: - docker: - - image: cimg/python:3.7-browsers - steps: - - test_core: - py: "37" - python_38_core: docker: - image: cimg/python:3.8-browsers @@ -181,20 +167,28 @@ jobs: - test_core: py: "39" - # Optional - python_36_optional: + python_310_core: docker: - - image: cimg/python:3.6-browsers + - image: cimg/python:3.10-browsers steps: - - test_optional: - py: "36" + - test_core: + py: "310" - python_37_optional: + python_311_core: docker: - - image: cimg/python:3.7-browsers + - image: cimg/python:3.11-browsers steps: - - test_optional: - py: "37" + - test_core: + py: "311" + + python_312_core: + docker: + - image: cimg/python:3.12-browsers + steps: + - test_core: + py: "312" + + # Optional python_38_optional: docker: @@ -210,6 +204,27 @@ jobs: - test_optional: py: "39" + python_310_optional: + docker: + - image: cimg/python:3.10-browsers + steps: + - test_optional: + py: "310" + + python_311_optional: + docker: + - image: cimg/python:3.11-browsers + steps: + - test_optional: + py: "311" + + python_312_optional: + docker: + - image: cimg/python:3.12-browsers + steps: + - test_optional: + py: "312" + # Pandas python_39_pandas_2_optional: @@ -228,7 +243,7 @@ jobs: py: "38" # Percy - python_37_percy: + python_39_percy: docker: - image: cimg/python:3.9-browsers environment: @@ -277,9 +292,9 @@ jobs: rm test/percy/*.html # Chart studio - python_37_chart_studio: + python_38_chart_studio: docker: - - image: cimg/python:3.7 + - image: cimg/python:3.8 resource_class: large steps: @@ -291,7 +306,7 @@ jobs: python -m venv venv . venv/bin/activate pip install --upgrade pip wheel - pip install -r ./test_requirements/requirements_37.txt + pip install -r ./test_requirements/requirements_38.txt - run: name: Tests command: | @@ -302,7 +317,7 @@ jobs: plotlyjs_dev_build: docker: - - image: cimg/python:3.7-node + - image: cimg/python:3.8-node environment: LANG: en_US.UTF-8 resource_class: large @@ -316,7 +331,7 @@ jobs: python -m venv venv . venv/bin/activate pip install --upgrade pip wheel - pip install -r ./test_requirements/requirements_37_core.txt black inflect + pip install -r ./test_requirements/requirements_38_core.txt black inflect pip install jupyterlab~=3.0 - run: name: Update jupyterlab-plotly version @@ -433,7 +448,7 @@ jobs: docker: # specify the version you desire here # use `-browsers` prefix for selenium tests, for example, `3.6.1-browsers` - - image: cimg/python:3.7-browsers + - image: cimg/python:3.9-browsers steps: - add_ssh_keys: @@ -570,15 +585,17 @@ workflows: build: jobs: - - python_36_core - - python_37_core - python_38_core - python_39_core - - python_36_optional - - python_37_optional + - python_310_core + - python_311_core + - python_312_core - python_38_optional - python_39_optional + - python_310_optional + - python_311_optional + - python_312_optional - python_39_pandas_2_optional - python_38_orca - - python_37_percy + - python_39_percy - build-doc diff --git a/binder/requirements.txt b/binder/requirements.txt index 38f2ca900a3..814206bdb34 100644 --- a/binder/requirements.txt +++ b/binder/requirements.txt @@ -2,8 +2,8 @@ jupytext plotly==5.18.0 jupyter notebook -pandas==1.0.3 -statsmodels==0.11.1 +pandas==1.1.3 +statsmodels==0.12.1 scipy patsy==0.5.1 numpy diff --git a/doc/python/county-choropleth.md b/doc/python/county-choropleth.md index ffed5efedc6..1340b4a75ad 100644 --- a/doc/python/county-choropleth.md +++ b/doc/python/county-choropleth.md @@ -47,9 +47,9 @@ Run the following commands to install the correct versions of the following modu ```python !pip install plotly-geo==1.0.0 -!pip install geopandas==0.3.0 -!pip install pyshp==1.2.10 -!pip install shapely==1.6.3 +!pip install geopandas==0.8.1 +!pip install pyshp==2.1.2 +!pip install shapely==1.7.1 ``` If you are using Windows, follow this post to properly install geopandas and dependencies: http://geoffboeing.com/2014/09/using-geopandas-windows/. If you are using Anaconda, do not use PIP to install the packages above. Instead use conda to install them: diff --git a/doc/python/ml-pca.md b/doc/python/ml-pca.md index cbd38e0fd9c..2160f671395 100644 --- a/doc/python/ml-pca.md +++ b/doc/python/ml-pca.md @@ -6,9 +6,9 @@ jupyter: extension: .md format_name: markdown format_version: '1.3' - jupytext_version: 1.14.1 + jupytext_version: 1.16.1 kernelspec: - display_name: Python 3 + display_name: Python 3 (ipykernel) language: python name: python3 language_info: @@ -20,7 +20,7 @@ jupyter: name: python nbconvert_exporter: python pygments_lexer: ipython3 - version: 3.8.8 + version: 3.10.11 plotly: description: Visualize Principle Component Analysis (PCA) of your high-dimensional data in Python with Plotly. @@ -105,17 +105,18 @@ fig.show() When you will have too many features to visualize, you might be interested in only visualizing the most relevant components. Those components often capture a majority of the [explained variance](https://en.wikipedia.org/wiki/Explained_variation), which is a good way to tell if those components are sufficient for modelling this dataset. -In the example below, our dataset contains 10 features, but we only select the first 4 components, since they explain over 99% of the total variance. +In the example below, our dataset contains 8 features, but we only select the first 2 components. ```python import pandas as pd import plotly.express as px from sklearn.decomposition import PCA -from sklearn.datasets import load_boston +from sklearn.datasets import fetch_california_housing -boston = load_boston() -df = pd.DataFrame(boston.data, columns=boston.feature_names) -n_components = 4 +housing = fetch_california_housing(as_frame=True) +df = housing.data + +n_components = 2 pca = PCA(n_components=n_components) components = pca.fit_transform(df) @@ -127,7 +128,7 @@ labels['color'] = 'Median Price' fig = px.scatter_matrix( components, - color=boston.target, + color=housing.target, dimensions=range(n_components), labels=labels, title=f'Total Explained Variance: {total_var:.2f}%', @@ -136,6 +137,10 @@ fig.update_traces(diagonal_visible=False) fig.show() ``` +```python +df +``` + ## PCA analysis in Dash [Dash](https://plotly.com/dash/) is the best way to build analytical apps in Python using Plotly figures. To run the app below, run `pip install dash`, click "Download" to get the code and run `python app.py`. diff --git a/doc/python/ml-regression.md b/doc/python/ml-regression.md index 9b07a7a9851..b8be347943a 100644 --- a/doc/python/ml-regression.md +++ b/doc/python/ml-regression.md @@ -6,9 +6,9 @@ jupyter: extension: .md format_name: markdown format_version: '1.3' - jupytext_version: 1.14.1 + jupytext_version: 1.16.1 kernelspec: - display_name: Python 3 + display_name: Python 3 (ipykernel) language: python name: python3 language_info: @@ -20,7 +20,7 @@ jupyter: name: python nbconvert_exporter: python pygments_lexer: ipython3 - version: 3.8.8 + version: 3.10.0 plotly: description: Visualize regression in scikit-learn with Plotly. display_as: ai_ml @@ -412,6 +412,7 @@ import pandas as pd import plotly.express as px import plotly.graph_objects as go from sklearn.linear_model import LassoCV +from sklearn.preprocessing import StandardScaler N_FOLD = 6 @@ -421,9 +422,13 @@ X = df.drop(columns=['lifeExp', 'iso_num']) X = pd.get_dummies(X, columns=['country', 'continent', 'iso_alpha']) y = df['lifeExp'] +# Normalize the data +scaler = StandardScaler() +X_scaled = scaler.fit_transform(X) + # Train model to predict life expectancy -model = LassoCV(cv=N_FOLD, normalize=True) -model.fit(X, y) +model = LassoCV(cv=N_FOLD) +model.fit(X_scaled, y) mean_alphas = model.mse_path_.mean(axis=-1) fig = go.Figure([ diff --git a/doc/requirements.txt b/doc/requirements.txt index 58ab8f22b22..b329e22271b 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -4,9 +4,9 @@ ipywidgets==7.7.2 jupyter-client<7 jupyter notebook -pandas==1.0.3 -statsmodels==0.11.1 -scipy==1.3.1 +pandas==1.2.0 +statsmodels==0.12.1 +scipy==1.5.4 patsy==0.5.1 numpy==1.19.5 plotly-geo @@ -16,7 +16,7 @@ pyshp==2.1.2 shapely==1.7.1 psutil requests -networkx +networkx==2.6.3 squarify scikit-image==0.18.1 scikit-learn @@ -25,7 +25,7 @@ sphinx_bootstrap_theme recommonmark pathlib python-frontmatter -datashader +datashader==0.14.4 pyarrow cufflinks==0.17.3 kaleido diff --git a/packages/python/plotly/plotly/figure_factory/_county_choropleth.py b/packages/python/plotly/plotly/figure_factory/_county_choropleth.py index 65c5e5ffede..c646aa5b034 100644 --- a/packages/python/plotly/plotly/figure_factory/_county_choropleth.py +++ b/packages/python/plotly/plotly/figure_factory/_county_choropleth.py @@ -358,15 +358,15 @@ def _calculations( elif fips_polygon_map[f].type == "MultiPolygon": x = [ poly.simplify(simplify_county).exterior.xy[0].tolist() - for poly in fips_polygon_map[f] + for poly in fips_polygon_map[f].geoms ] y = [ poly.simplify(simplify_county).exterior.xy[1].tolist() - for poly in fips_polygon_map[f] + for poly in fips_polygon_map[f].geoms ] - x_c = [poly.centroid.xy[0].tolist() for poly in fips_polygon_map[f]] - y_c = [poly.centroid.xy[1].tolist() for poly in fips_polygon_map[f]] + x_c = [poly.centroid.xy[0].tolist() for poly in fips_polygon_map[f].geoms] + y_c = [poly.centroid.xy[1].tolist() for poly in fips_polygon_map[f].geoms] county_name_str = str(df[df["FIPS"] == f]["COUNTY_NAME"].iloc[0]) state_name_str = str(df[df["FIPS"] == f]["STATE_NAME"].iloc[0]) @@ -382,7 +382,7 @@ def _calculations( + "
Value: " + str(values[index]) ) - t_c = [text for poly in fips_polygon_map[f]] + t_c = [text for poly in fips_polygon_map[f].geoms] x_centroids = x_c + x_centroids y_centroids = y_c + y_centroids centroid_text = t_c + centroid_text @@ -853,11 +853,11 @@ def create_choropleth( elif df_state["geometry"][index].type == "MultiPolygon": x = [ poly.simplify(simplify_state).exterior.xy[0].tolist() - for poly in df_state["geometry"][index] + for poly in df_state["geometry"][index].geoms ] y = [ poly.simplify(simplify_state).exterior.xy[1].tolist() - for poly in df_state["geometry"][index] + for poly in df_state["geometry"][index].geoms ] for segment in range(len(x)): x_states = x_states + x[segment] diff --git a/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py b/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py index 8b899a6f5a3..c0c9a173203 100644 --- a/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py +++ b/packages/python/plotly/plotly/tests/test_core/test_errors/test_dict_path_errors.py @@ -1,6 +1,7 @@ import plotly.graph_objects as go from _plotly_utils.exceptions import PlotlyKeyError import pytest +import sys def error_substr(s, r): @@ -400,9 +401,11 @@ def test_described_subscript_error_on_type_error(some_fig): except ValueError as e: raised = True print(e.args[0]) + # Error is different in Python 3.11 and later + error_addition_py_311 = ", not 'str'" if sys.version_info >= (3, 11, 0) else "" e_substr = error_substr( e.args[0], - """string indices must be integers + f"""string indices must be integers{error_addition_py_311} Invalid value received for the 'plot_bgcolor' property of layout diff --git a/packages/python/plotly/plotly/tests/test_io/test_to_from_plotly_json.py b/packages/python/plotly/plotly/tests/test_io/test_to_from_plotly_json.py index ff8ad213c84..38bbcdaeceb 100644 --- a/packages/python/plotly/plotly/tests/test_io/test_to_from_plotly_json.py +++ b/packages/python/plotly/plotly/tests/test_io/test_to_from_plotly_json.py @@ -96,7 +96,7 @@ def numeric_numpy_array(request): @pytest.fixture(scope="module") def object_numpy_array(request): - return np.array(["a", 1, [2, 3]]) + return np.array(["a", 1, [2, 3]], dtype="object") @pytest.fixture(scope="module") diff --git a/packages/python/plotly/plotly/tests/test_optional/test_figure_factory/test_figure_factory.py b/packages/python/plotly/plotly/tests/test_optional/test_figure_factory/test_figure_factory.py index 8783dce1ab4..20a1b23f7ff 100644 --- a/packages/python/plotly/plotly/tests/test_optional/test_figure_factory/test_figure_factory.py +++ b/packages/python/plotly/plotly/tests/test_optional/test_figure_factory/test_figure_factory.py @@ -4310,6 +4310,33 @@ def test_optional_arguments(self): class TestHexbinMapbox(NumpyTestUtilsMixin, TestCaseNoTemplate): + def compare_list_values(self, list1, list2, decimal=7): + assert len(list1) == len(list2), "Lists are not of the same length." + for i in range(len(list1)): + if isinstance(list1[i], list): + self.compare_list_values(list1[i], list2[i], decimal=decimal) + elif isinstance(list1[i], dict): + self.compare_dict_values(list1[i], list2[i], decimal=decimal) + elif isinstance(list1[i], float): + np.testing.assert_almost_equal(list1[i], list2[i], decimal=decimal) + else: + assert ( + list1[i] == list2[i] + ), f"Values at index {i} are not equal: {list1[i]} != {list2[i]}" + + def compare_dict_values(self, dict1, dict2, decimal=7): + for k, v in dict1.items(): + if isinstance(v, dict): + self.compare_dict_values(v, dict2[k], decimal=decimal) + elif isinstance(v, list): + self.compare_list_values(v, dict2[k], decimal=decimal) + elif isinstance(v, float): + np.testing.assert_almost_equal(v, dict2[k], decimal=decimal) + else: + assert ( + v == dict2[k] + ), f"Values for key {k} are not equal: {v} != {dict2[k]}" + def test_aggregation(self): lat = [0, 1, 1, 2, 4, 5, 1, 2, 4, 5, 2, 3, 2, 1, 5, 3, 5] @@ -4416,7 +4443,7 @@ def test_aggregation(self): actual_agg = [2.0, 2.0, 1.0, 3.0, 9.0] - self.assert_dict_equal(fig1.data[0].geojson, actual_geojson) + self.compare_dict_values(fig1.data[0].geojson, actual_geojson) assert np.array_equal(fig1.data[0].z, actual_agg) fig2 = ff.create_hexbin_mapbox( diff --git a/packages/python/plotly/plotly/tests/test_optional/test_px/test_px_input.py b/packages/python/plotly/plotly/tests/test_optional/test_px/test_px_input.py index 94382cb9e01..8f8de64a2de 100644 --- a/packages/python/plotly/plotly/tests/test_optional/test_px/test_px_input.py +++ b/packages/python/plotly/plotly/tests/test_optional/test_px/test_px_input.py @@ -7,6 +7,7 @@ import unittest.mock as mock from plotly.express._core import build_dataframe from pandas.testing import assert_frame_equal +import sys # Fixtures @@ -318,7 +319,8 @@ def __dataframe__(self): @pytest.mark.skipif( - version.parse(pd.__version__) < version.parse("2.0.2"), + version.parse(pd.__version__) < version.parse("2.0.2") + or sys.version_info >= (3, 12), reason="plotly doesn't use a dataframe interchange protocol for pandas < 2.0.2", ) @pytest.mark.parametrize("test_lib", ["vaex", "polars"]) @@ -339,7 +341,8 @@ def test_build_df_from_vaex_and_polars(test_lib): @pytest.mark.skipif( - version.parse(pd.__version__) < version.parse("2.0.2"), + version.parse(pd.__version__) < version.parse("2.0.2") + or sys.version_info >= (3, 12), reason="plotly doesn't use a dataframe interchange protocol for pandas < 2.0.2", ) @pytest.mark.parametrize("test_lib", ["vaex", "polars"]) diff --git a/packages/python/plotly/recipe/conda_build_config.yaml b/packages/python/plotly/recipe/conda_build_config.yaml index abcf8353472..86b425eca79 100644 --- a/packages/python/plotly/recipe/conda_build_config.yaml +++ b/packages/python/plotly/recipe/conda_build_config.yaml @@ -1,2 +1,2 @@ python: - - 3.6 + - 3.8 diff --git a/packages/python/plotly/setup.py b/packages/python/plotly/setup.py index a9a2ac5869f..e1247b9f937 100644 --- a/packages/python/plotly/setup.py +++ b/packages/python/plotly/setup.py @@ -217,8 +217,8 @@ def finalize_options(self): pass def run(self): - if sys.version_info < (3, 6): - raise ImportError("Code generation must be executed with Python >= 3.6") + if sys.version_info < (3, 8): + raise ImportError("Code generation must be executed with Python >= 3.8") from codegen import perform_codegen @@ -506,16 +506,15 @@ def run(self): long_description_content_type="text/markdown", classifiers=[ "Development Status :: 5 - Production/Stable", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Scientific/Engineering :: Visualization", "License :: OSI Approved :: MIT License", ], - python_requires=">=3.6", + python_requires=">=3.8", license="MIT", packages=[ "jupyterlab_plotly", diff --git a/packages/python/plotly/test_requirements/requirements_310_core.txt b/packages/python/plotly/test_requirements/requirements_310_core.txt new file mode 100644 index 00000000000..fcacda06c79 --- /dev/null +++ b/packages/python/plotly/test_requirements/requirements_310_core.txt @@ -0,0 +1,3 @@ +requests==2.25.1 +tenacity==6.2.0 +pytest==7.4.4 diff --git a/packages/python/plotly/test_requirements/requirements_310_optional.txt b/packages/python/plotly/test_requirements/requirements_310_optional.txt new file mode 100644 index 00000000000..105eda9b252 --- /dev/null +++ b/packages/python/plotly/test_requirements/requirements_310_optional.txt @@ -0,0 +1,22 @@ +requests==2.25.1 +tenacity==6.2.0 +pandas==1.5.3 +numpy==1.23.0 +xarray==0.17.0 +statsmodels==0.14.1 +Pillow==10.2.0 +pytest==7.4.4 +pytz==2023.3.post1 +ipython[all]==7.22.0 +ipywidgets==8.0.2 +ipykernel==5.5.3 +jupyter==1.0.0 +scipy==1.11.4 +Shapely==1.8.5 +geopandas==0.9.0 +pyshp==2.1.3 +matplotlib==3.8.0 +scikit-image==0.22.0 +psutil==5.7.0 +kaleido +orjson==3.8.12 diff --git a/packages/python/plotly/test_requirements/requirements_311_core.txt b/packages/python/plotly/test_requirements/requirements_311_core.txt new file mode 100644 index 00000000000..fcacda06c79 --- /dev/null +++ b/packages/python/plotly/test_requirements/requirements_311_core.txt @@ -0,0 +1,3 @@ +requests==2.25.1 +tenacity==6.2.0 +pytest==7.4.4 diff --git a/packages/python/plotly/test_requirements/requirements_311_optional.txt b/packages/python/plotly/test_requirements/requirements_311_optional.txt new file mode 100644 index 00000000000..800dbeb6a25 --- /dev/null +++ b/packages/python/plotly/test_requirements/requirements_311_optional.txt @@ -0,0 +1,22 @@ +requests==2.25.1 +tenacity==6.2.0 +pandas==1.5.3 +numpy==1.23.2 +xarray==0.17.0 +statsmodels==0.14.1 +Pillow==10.2.0 +pytest==7.4.4 +pytz==2023.3.post1 +ipython[all]==7.22.0 +ipywidgets==8.0.2 +ipykernel==5.5.3 +jupyter==1.0.0 +scipy==1.11.4 +Shapely==1.8.5 +geopandas==0.9.0 +pyshp==2.1.3 +matplotlib==3.8.0 +scikit-image==0.22.0 +psutil==5.7.0 +kaleido +orjson==3.8.12 diff --git a/packages/python/plotly/test_requirements/requirements_312_core.txt b/packages/python/plotly/test_requirements/requirements_312_core.txt new file mode 100644 index 00000000000..fcacda06c79 --- /dev/null +++ b/packages/python/plotly/test_requirements/requirements_312_core.txt @@ -0,0 +1,3 @@ +requests==2.25.1 +tenacity==6.2.0 +pytest==7.4.4 diff --git a/packages/python/plotly/test_requirements/requirements_312_optional.txt b/packages/python/plotly/test_requirements/requirements_312_optional.txt new file mode 100644 index 00000000000..05185830b85 --- /dev/null +++ b/packages/python/plotly/test_requirements/requirements_312_optional.txt @@ -0,0 +1,22 @@ +requests==2.31.0 +tenacity==8.2.3 +pandas +numpy +xarray==2023.12.0 +statsmodels +Pillow==10.2.0 +pytest==7.4.4 +pytz==2023.3.post1 +ipython[all]==7.22.0 +ipywidgets<8 +ipykernel==5.5.3 +jupyter==1.0.0 +scipy==1.11.4 +Shapely==2.0.2 +geopandas==0.14.2 +pyshp==2.3.1 +matplotlib==3.8.2 +scikit-image==0.22.0 +psutil==5.9.7 +kaleido +orjson==3.9.10 \ No newline at end of file