Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(example): Adds Confidence Interval Ellipses #3747

Open
wants to merge 31 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
a22e8dc
Create deviation_ellipses.py
Feb 26, 2018
1983ede
docs: Initial rewrite of (#514)
dangotbanned Jan 4, 2025
dc7639d
ci(typing): Adds `scipy-stubs` to `altair[doc]`
dangotbanned Jan 4, 2025
a296b82
fix: Only install `scipy-stubs` on `>=3.10`
dangotbanned Jan 4, 2025
eb25871
chore(typing): Ignore incorrect `pandas` stubs
dangotbanned Jan 4, 2025
279fca5
ci(typing): ignore `scipy` on `3.9`
dangotbanned Jan 4, 2025
4944b80
docs: Add missing category
dangotbanned Jan 4, 2025
7cd2a77
fix: Add missing support for `from __future__ import annotations`
dangotbanned Jan 4, 2025
be087d2
test: skip example when `scipy` not installed
dangotbanned Jan 4, 2025
1623629
docs: reduce segments `100` -> `50`
dangotbanned Jan 4, 2025
a357668
docs: Clean up `numpy`, `scipy` docs/comments
dangotbanned Jan 4, 2025
ac34139
refactor: Simplify `numpy` transforms
dangotbanned Jan 4, 2025
e0e276b
docs: add tooltip, increase size
dangotbanned Jan 4, 2025
dc0ae52
fix: Remove incorrect range stop
dangotbanned Jan 5, 2025
4969a98
refactor: Remove special casing `__future__` import
dangotbanned Jan 5, 2025
dcb9fa5
docs: Remove unused `method` code
dangotbanned Jan 5, 2025
bd4e30f
docs: rename to 'Confidence Interval Ellipses'
dangotbanned Jan 5, 2025
62927af
docs: add references to description
dangotbanned Jan 5, 2025
250d2b4
Merge branch 'main' into vegalite_v2_examples
dangotbanned Jan 6, 2025
fadb2e3
Merge branch 'main' into vegalite_v2_examples
dangotbanned Jan 6, 2025
5886aae
Merge branch 'main' into vegalite_v2_examples
dangotbanned Jan 9, 2025
714e67f
Merge remote-tracking branch 'upstream/main' into vegalite_v2_examples
dangotbanned Jan 10, 2025
182baea
docs: Adds methods syntax version
dangotbanned Jan 10, 2025
fa280f1
refactor: Rewrite `pd_ellipse`
dangotbanned Jan 10, 2025
c578ac1
Merge branch 'main' into vegalite_v2_examples
dangotbanned Jan 16, 2025
40f2402
Merge remote-tracking branch 'upstream/main' into vegalite_v2_examples
dangotbanned Jan 17, 2025
f72da5a
ci(uv): sync `scipy-stubs`
dangotbanned Jan 17, 2025
3debf11
refactor(typing): Try removing `from __future__ import annotations`
dangotbanned Jan 17, 2025
e6c9c5f
refactor: rename `np_ellipse` -> `confidence_region_2d`
dangotbanned Jan 17, 2025
7401a08
refactor: rename `pd_ellipse` -> `grouped_confidence_regions`
dangotbanned Jan 17, 2025
66c4f81
docs: change category to `"case studies"`
dangotbanned Jan 17, 2025
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
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ doc = [
"sphinx_copybutton",
"sphinx-design",
"scipy",
"scipy-stubs; python_version>=\"3.10\"",
]

[tool.altair.vega]
Expand Down Expand Up @@ -458,8 +459,10 @@ module = [
"ipykernel.*",
"ibis.*",
"vegafusion.*",
"scipy.*"
]
ignore_missing_imports = true
disable_error_code = ["import-untyped"]

[tool.pyright]
enableExperimentalFeatures=true
Expand Down
6 changes: 3 additions & 3 deletions sphinxext/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def create_generic_image(
"""


def _parse_source_file(filename: str) -> tuple[ast.Module | None, str]:
def _parse_source_file(filename: str | Path) -> tuple[ast.Module | None, str]:
"""
Parse source file into AST node.

Expand Down Expand Up @@ -88,7 +88,7 @@ def _parse_source_file(filename: str) -> tuple[ast.Module | None, str]:
return node, content


def get_docstring_and_rest(filename: str) -> tuple[str, str | None, str, int]:
def get_docstring_and_rest(filename: str | Path) -> tuple[str, str | None, str, int]:
"""
Separate ``filename`` content between docstring and the rest.

Expand Down Expand Up @@ -160,7 +160,7 @@ def get_docstring_and_rest(filename: str) -> tuple[str, str | None, str, int]:
if (
node.body
and isinstance(node.body[0], ast.Expr)
and isinstance(node.body[0].value, (ast.Str, ast.Constant))
and isinstance(node.body[0].value, ast.Constant)
):
docstring_node = node.body[0]
docstring = docstring_node.value.s # pyright: ignore[reportAttributeAccessIssue]
Expand Down
5 changes: 5 additions & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ def windows_has_tzdata() -> bool:
https://github.com/vega/vegafusion
"""

skip_requires_scipy: pytest.MarkDecorator = pytest.mark.skipif(
find_spec("scipy") is None, reason="`scipy` not installed."
)


def skip_requires_pyarrow(
fn: Callable[..., Any] | None = None, /, *, requires_tzdata: bool = False
Expand Down Expand Up @@ -229,6 +233,7 @@ def _distributed_examples(
"wind_vector_map": slow,
r"\.point_map\.py": slow,
"line_chart_with_color_datum": slow,
"deviation_ellipses": skip_requires_scipy,
},
)
),
Expand Down
91 changes: 91 additions & 0 deletions tests/examples_arguments_syntax/deviation_ellipses.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"""
Confidence Interval Ellipses
----------------------------
This example shows bivariate deviation ellipses of petal length and width of three iris species.

Inspired by `ggplot2.stat_ellipse`_ and directly based on `Deviation ellipses example`_ by `@essicolo`_

.. _ggplot2.stat_ellipse:
https://ggplot2.tidyverse.org/reference/stat_ellipse.html#ref-examples
.. _Deviation ellipses example:
https://github.com/vega/altair/pull/514
.. _@essicolo:
https://github.com/essicolo
"""

# category: distributions
from __future__ import annotations

import numpy as np
import pandas as pd
from scipy.stats import f as F

import altair as alt
from vega_datasets import data


def np_ellipse(
arr: np.ndarray[tuple[int, int], np.dtype[np.float64]],
conf_level: float = 0.95,
segments: int = 50,
):
"""
Calculate confidence interval ellipse.

Parameters
----------
arr
numpy array with 2 columns
conf_level
lower tail probability
segments
number of points describing the ellipse.
"""
n_elements = len(arr)
# Degrees of freedom of the chi-squared distribution in the **numerator**
dfn = 2
# Degrees of freedom of the chi-squared distribution in the **denominator**
dfd = n_elements - 1
# Percent point function at `conf_level` of an F continuous random variable
quantile = F.ppf(conf_level, dfn=dfn, dfd=dfd)
radius = np.sqrt(2 * quantile)
angles = np.arange(0, segments) * 2 * np.pi / segments
circle = np.column_stack((np.cos(angles), np.sin(angles)))
center = np.mean(arr, axis=0)
cov_mat = np.cov(arr, rowvar=False)
return center + radius * (circle @ np.linalg.cholesky(cov_mat).T)


def pd_ellipse(
df: pd.DataFrame, col_x: str, col_y: str, col_group: str
) -> pd.DataFrame:
cols = col_x, col_y
groups = []
# TODO: Rewrite in a more readable way
categories = df[col_group].unique()
dangotbanned marked this conversation as resolved.
Show resolved Hide resolved
for category in categories:
sliced = df.loc[df[col_group] == category, cols]
ell_df = pd.DataFrame(np_ellipse(sliced.to_numpy()), columns=cols) # type: ignore
ell_df[col_group] = category
groups.append(ell_df)
return pd.concat(groups).reset_index()
dangotbanned marked this conversation as resolved.
Show resolved Hide resolved


col_x = "petalLength"
col_y = "petalWidth"
col_group = "species"
x = alt.X(col_x, scale=alt.Scale(zero=False))
y = alt.Y(col_y, scale=alt.Scale(zero=False))
color = alt.Color(col_group)

source = data.iris()
ellipse = pd_ellipse(source, col_x=col_x, col_y=col_y, col_group=col_group)
points = alt.Chart(source).mark_circle(size=50, tooltip=True).encode(x, y, color)
lines = (
alt.Chart(ellipse)
.mark_line(filled=True, fillOpacity=0.2)
.encode(x, y, color, order="index")
)

chart = (lines + points).properties(height=500, width=500)
chart
Loading