plotnine 0.15.0.dev3__py3-none-any.whl → 0.15.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. plotnine/__init__.py +2 -0
  2. plotnine/_mpl/layout_manager/_engine.py +1 -1
  3. plotnine/_mpl/layout_manager/_layout_items.py +126 -41
  4. plotnine/_mpl/layout_manager/_layout_tree.py +712 -314
  5. plotnine/_mpl/layout_manager/_spaces.py +305 -101
  6. plotnine/_mpl/patches.py +70 -34
  7. plotnine/_mpl/text.py +144 -63
  8. plotnine/_mpl/utils.py +1 -1
  9. plotnine/_utils/__init__.py +50 -107
  10. plotnine/_utils/context.py +78 -2
  11. plotnine/_utils/ipython.py +35 -51
  12. plotnine/_utils/quarto.py +26 -0
  13. plotnine/_utils/yippie.py +115 -0
  14. plotnine/composition/__init__.py +11 -0
  15. plotnine/composition/_beside.py +55 -0
  16. plotnine/composition/_compose.py +471 -0
  17. plotnine/composition/_plot_spacer.py +60 -0
  18. plotnine/composition/_stack.py +55 -0
  19. plotnine/coords/coord.py +3 -3
  20. plotnine/data/__init__.py +31 -0
  21. plotnine/data/anscombe-quartet.csv +45 -0
  22. plotnine/doctools.py +4 -4
  23. plotnine/facets/facet.py +4 -4
  24. plotnine/facets/strips.py +17 -28
  25. plotnine/geoms/annotate.py +13 -13
  26. plotnine/geoms/annotation_logticks.py +7 -8
  27. plotnine/geoms/annotation_stripes.py +6 -6
  28. plotnine/geoms/geom.py +60 -27
  29. plotnine/geoms/geom_abline.py +3 -2
  30. plotnine/geoms/geom_area.py +2 -2
  31. plotnine/geoms/geom_bar.py +1 -0
  32. plotnine/geoms/geom_bin_2d.py +6 -2
  33. plotnine/geoms/geom_blank.py +0 -3
  34. plotnine/geoms/geom_boxplot.py +8 -4
  35. plotnine/geoms/geom_col.py +2 -2
  36. plotnine/geoms/geom_count.py +6 -2
  37. plotnine/geoms/geom_crossbar.py +3 -3
  38. plotnine/geoms/geom_density_2d.py +6 -2
  39. plotnine/geoms/geom_dotplot.py +2 -2
  40. plotnine/geoms/geom_errorbar.py +2 -2
  41. plotnine/geoms/geom_errorbarh.py +2 -2
  42. plotnine/geoms/geom_histogram.py +1 -1
  43. plotnine/geoms/geom_hline.py +3 -2
  44. plotnine/geoms/geom_linerange.py +2 -2
  45. plotnine/geoms/geom_map.py +5 -5
  46. plotnine/geoms/geom_path.py +11 -12
  47. plotnine/geoms/geom_point.py +4 -5
  48. plotnine/geoms/geom_pointdensity.py +4 -0
  49. plotnine/geoms/geom_pointrange.py +3 -5
  50. plotnine/geoms/geom_polygon.py +2 -3
  51. plotnine/geoms/geom_qq.py +4 -0
  52. plotnine/geoms/geom_qq_line.py +4 -0
  53. plotnine/geoms/geom_quantile.py +4 -0
  54. plotnine/geoms/geom_raster.py +4 -5
  55. plotnine/geoms/geom_rect.py +3 -4
  56. plotnine/geoms/geom_ribbon.py +7 -7
  57. plotnine/geoms/geom_rug.py +1 -1
  58. plotnine/geoms/geom_segment.py +2 -2
  59. plotnine/geoms/geom_sina.py +3 -3
  60. plotnine/geoms/geom_smooth.py +7 -3
  61. plotnine/geoms/geom_step.py +2 -2
  62. plotnine/geoms/geom_text.py +2 -3
  63. plotnine/geoms/geom_violin.py +8 -5
  64. plotnine/geoms/geom_vline.py +3 -2
  65. plotnine/ggplot.py +64 -85
  66. plotnine/guides/guide.py +7 -10
  67. plotnine/guides/guide_colorbar.py +3 -3
  68. plotnine/guides/guide_legend.py +3 -3
  69. plotnine/guides/guides.py +6 -6
  70. plotnine/helpers.py +49 -0
  71. plotnine/iapi.py +28 -5
  72. plotnine/labels.py +3 -3
  73. plotnine/layer.py +36 -19
  74. plotnine/mapping/_atomic.py +178 -0
  75. plotnine/mapping/_env.py +13 -2
  76. plotnine/mapping/_eval_environment.py +1 -1
  77. plotnine/mapping/aes.py +85 -49
  78. plotnine/scales/__init__.py +2 -0
  79. plotnine/scales/limits.py +7 -7
  80. plotnine/scales/scale.py +3 -3
  81. plotnine/scales/scale_color.py +82 -18
  82. plotnine/scales/scale_continuous.py +6 -4
  83. plotnine/scales/scale_datetime.py +28 -14
  84. plotnine/scales/scale_discrete.py +1 -1
  85. plotnine/scales/scale_identity.py +21 -2
  86. plotnine/scales/scale_manual.py +8 -2
  87. plotnine/scales/scale_xy.py +2 -2
  88. plotnine/stats/binning.py +4 -1
  89. plotnine/stats/smoothers.py +23 -36
  90. plotnine/stats/stat.py +20 -32
  91. plotnine/stats/stat_bin.py +6 -5
  92. plotnine/stats/stat_bin_2d.py +11 -9
  93. plotnine/stats/stat_bindot.py +13 -16
  94. plotnine/stats/stat_boxplot.py +6 -6
  95. plotnine/stats/stat_count.py +6 -9
  96. plotnine/stats/stat_density.py +7 -10
  97. plotnine/stats/stat_density_2d.py +12 -8
  98. plotnine/stats/stat_ecdf.py +7 -6
  99. plotnine/stats/stat_ellipse.py +9 -6
  100. plotnine/stats/stat_function.py +10 -8
  101. plotnine/stats/stat_hull.py +6 -3
  102. plotnine/stats/stat_identity.py +5 -2
  103. plotnine/stats/stat_pointdensity.py +5 -7
  104. plotnine/stats/stat_qq.py +46 -20
  105. plotnine/stats/stat_qq_line.py +16 -11
  106. plotnine/stats/stat_quantile.py +15 -9
  107. plotnine/stats/stat_sina.py +13 -15
  108. plotnine/stats/stat_smooth.py +8 -10
  109. plotnine/stats/stat_sum.py +5 -2
  110. plotnine/stats/stat_summary.py +7 -10
  111. plotnine/stats/stat_summary_bin.py +11 -14
  112. plotnine/stats/stat_unique.py +5 -2
  113. plotnine/stats/stat_ydensity.py +8 -11
  114. plotnine/themes/elements/__init__.py +2 -1
  115. plotnine/themes/elements/element_line.py +17 -9
  116. plotnine/themes/elements/margin.py +64 -1
  117. plotnine/themes/theme.py +9 -1
  118. plotnine/themes/theme_538.py +0 -1
  119. plotnine/themes/theme_bw.py +0 -1
  120. plotnine/themes/theme_dark.py +0 -1
  121. plotnine/themes/theme_gray.py +6 -5
  122. plotnine/themes/theme_light.py +1 -1
  123. plotnine/themes/theme_matplotlib.py +5 -5
  124. plotnine/themes/theme_seaborn.py +7 -4
  125. plotnine/themes/theme_void.py +9 -8
  126. plotnine/themes/theme_xkcd.py +0 -1
  127. plotnine/themes/themeable.py +109 -31
  128. plotnine/typing.py +17 -6
  129. plotnine/watermark.py +3 -3
  130. {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/METADATA +13 -6
  131. plotnine-0.15.2.dist-info/RECORD +221 -0
  132. {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/WHEEL +1 -1
  133. plotnine/plot_composition/__init__.py +0 -10
  134. plotnine/plot_composition/_compose.py +0 -436
  135. plotnine/plot_composition/_spacer.py +0 -32
  136. plotnine-0.15.0.dev3.dist-info/RECORD +0 -215
  137. /plotnine/{plot_composition → composition}/_plotspec.py +0 -0
  138. {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/licenses/LICENSE +0 -0
  139. {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from dataclasses import KW_ONLY, InitVar, dataclass
3
+ from dataclasses import KW_ONLY, InitVar, dataclass, field
4
4
  from typing import Literal, Sequence
5
5
  from warnings import warn
6
6
 
@@ -48,36 +48,72 @@ class _scale_color_continuous(
48
48
  class scale_color_hue(_scale_color_discrete):
49
49
  """
50
50
  Qualitative color scale with evenly spaced hues
51
+
52
+ See Also
53
+ --------
54
+ mizani.palettes.hue_pal : The palette class that generates colours
55
+ in HCL space.
51
56
  """
52
57
 
53
- h: InitVar[float] = 0.01
58
+ h: InitVar[float | tuple[float, float]] = 15
54
59
  """
55
- Hue. Must be in the range [0, 1]
60
+ Hue. If a float, it is the first hue value, in the range `[0, 360]`.
61
+ The range of the palette will be `[first, first + 360)`.
62
+
63
+ If a tuple, it is the range `[first, last)` of the hues.
56
64
  """
57
65
 
58
- l: InitVar[float] = 0.6
66
+ c: InitVar[float] = 100
59
67
  """
60
- Lightness. Must be in the range [0, 1]
68
+ Chroma. Must be in the range `[0, 100]`
61
69
  """
62
70
 
63
- s: InitVar[float] = 0.65
71
+ l: InitVar[float] = 65
64
72
  """
65
- Saturation. Must be in the range [0, 1]
73
+ Lightness. Must be in the range [0, 100]
66
74
  """
67
75
 
68
- color_space: InitVar[Literal["hls", "hsluv"]] = "hls"
76
+ direction: InitVar[Literal[1, -1]] = 1
69
77
  """
70
- Color space to use. Should be one of
71
- [hls](https://en.wikipedia.org/wiki/HSL_and_HSV)
72
- or [hsluv](https://www.hsluv.org/).
73
- https://www.hsluv.org/
78
+ The order of colours in the scale. If -1 the order
79
+ of colours is reversed. The default is 1.
74
80
  """
75
81
 
76
- def __post_init__(self, h, l, s, color_space):
82
+ _: KW_ONLY
83
+
84
+ s: None = field(default=None, repr=False)
85
+ """
86
+ Not being used and will be removed in a future version
87
+ """
88
+ color_space: None = field(default=None, repr=False)
89
+ """
90
+ Not being used and will be removed in a future version
91
+ """
92
+
93
+ def __post_init__(self, h, c, l, direction):
77
94
  from mizani.palettes import hue_pal
78
95
 
96
+ if (s := self.s) is not None:
97
+ warn(
98
+ f"You used {s=} for the saturation which has been ignored. "
99
+ f"{self.__class__.__name__} now works in HCL colorspace. "
100
+ f"Using `s` in future versions will throw an exception.",
101
+ FutureWarning,
102
+ )
103
+ del self.s
104
+
105
+ if (color_space := self.color_space) is not None:
106
+ warn(
107
+ f"You used {color_space=} to select a color_space and it "
108
+ f"has been ignored. {self.__class__.__name__} now only works "
109
+ f"in HCL colorspace. Using `color_space` in future versions "
110
+ "will throw an exception.",
111
+ FutureWarning,
112
+ )
113
+ del self.color_space
114
+
79
115
  super().__post_init__()
80
- self.palette = hue_pal(h, l, s, color_space=color_space)
116
+ self.palette = hue_pal(h, c, l, direction)
81
117
 
82
118
 
83
119
  @dataclass
@@ -95,6 +131,11 @@ class scale_color_brewer(_scale_color_discrete):
95
131
  Sequential, diverging and qualitative discrete color scales
96
132
 
97
133
  See `colorbrewer.org <http://colorbrewer2.org/>`_
134
+
135
+ See Also
136
+ --------
137
+ mizani.palette.brewer_pal : The palette class that generates colours
138
+ that generates the brewer colors.
98
139
  """
99
140
 
100
141
  type: InitVar[
@@ -146,6 +187,11 @@ class scale_fill_brewer(scale_color_brewer):
146
187
  class scale_color_grey(_scale_color_discrete):
147
188
  """
148
189
  Sequential grey color scale.
190
+
191
+ See Also
192
+ --------
193
+ mizani.palettes.grey_pal : The palette class that generates colours
194
+ gray scale color.
149
195
  """
150
196
 
151
197
  start: InitVar[float] = 0.2
@@ -188,6 +234,8 @@ class scale_color_gradient(_scale_color_continuous):
188
234
  --------
189
235
  plotnine.scale_color_gradient2
190
236
  plotnine.scale_color_gradientn
237
+ mizani.palettes.gradient_n_pal : The palette class that generates
238
+ the colour gradient.
191
239
  """
192
240
 
193
241
  low: InitVar[str] = "#132B43"
@@ -220,6 +268,11 @@ class scale_fill_gradient(scale_color_gradient):
220
268
  class scale_color_desaturate(_scale_color_continuous):
221
269
  """
222
270
  Create a desaturated color gradient
271
+
272
+ See Also
273
+ --------
274
+ mizani.palettes.desaturate_pal : The palette class that generates
275
+ the desaturated colours.
223
276
  """
224
277
 
225
278
  color: InitVar[str] = "red"
@@ -263,6 +316,8 @@ class scale_color_gradient2(_scale_color_continuous):
263
316
  --------
264
317
  plotnine.scale_color_gradient
265
318
  plotnine.scale_color_gradientn
319
+ mizani.palettes.gradient_n_pal : The palette class that generates
320
+ the colour gradient.
266
321
  """
267
322
 
268
323
  low: InitVar[str] = "#832424"
@@ -316,9 +371,11 @@ class scale_color_gradientn(_scale_color_continuous):
316
371
  --------
317
372
  plotnine.scale_color_gradient
318
373
  plotnine.scale_color_gradientn
374
+ mizani.palettes.gradient_n_pal : The palette class that generates
375
+ the colour gradient.
319
376
  """
320
377
 
321
- colors: InitVar[Sequence[str]] = "#832424"
378
+ colors: InitVar[Sequence[str]]
322
379
  """
323
380
  List of colors
324
381
  """
@@ -332,8 +389,8 @@ class scale_color_gradientn(_scale_color_continuous):
332
389
  def __post_init__(self, colors, values):
333
390
  from mizani.palettes import gradient_n_pal
334
391
 
335
- self.palette = gradient_n_pal(colors, values)
336
392
  super().__post_init__()
393
+ self.palette = gradient_n_pal(colors, values)
337
394
 
338
395
 
339
396
  @dataclass
@@ -430,6 +487,8 @@ class scale_color_cmap(_scale_color_continuous):
430
487
  --------
431
488
  [](`matplotlib.cm`)
432
489
  [](`matplotlib.colors`)
490
+ mizani.palettes.cmap_pal : The palette class that generates
491
+ the colour gradients of this scale.
433
492
  """
434
493
 
435
494
  cmap_name: InitVar[str] = "viridis"
@@ -457,7 +516,7 @@ class scale_fill_cmap(scale_color_cmap):
457
516
 
458
517
 
459
518
  @dataclass
460
- class scale_color_cmap_d(scale_discrete):
519
+ class scale_color_cmap_d(_scale_color_discrete):
461
520
  """
462
521
  A discrete color scales using Matplotlib colormaps
463
522
 
@@ -465,6 +524,8 @@ class scale_color_cmap_d(scale_discrete):
465
524
  --------
466
525
  [](`matplotlib.cm`)
467
526
  [](`matplotlib.colors`)
527
+ mizani.palettes.cmap_pal : The palette class that generates
528
+ the colours of this scale.
468
529
  """
469
530
 
470
531
  cmap_name: InitVar[str] = "viridis"
@@ -474,7 +535,6 @@ class scale_color_cmap_d(scale_discrete):
474
535
  `matplotlib.cm.cmap_d.keys()` or see the
475
536
  `documentation <http://matplotlib.org/users/colormaps.html>`_.
476
537
  """
477
- _aesthetics = ["color"]
478
538
 
479
539
  def __post_init__(self, cmap_name):
480
540
  from mizani.palettes import cmap_d_pal
@@ -496,6 +556,10 @@ class scale_fill_cmap_d(scale_color_cmap_d):
496
556
  class scale_color_datetime(scale_datetime, scale_color_cmap): # pyright: ignore[reportIncompatibleVariableOverride]
497
557
  """
498
558
  Datetime color scale
559
+
560
+ See Also
561
+ --------
562
+ plotnine.scale_color_cmap : The parent class.
499
563
  """
500
564
 
501
565
  _: KW_ONLY
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from contextlib import suppress
4
4
  from dataclasses import dataclass
5
- from typing import TYPE_CHECKING, Sequence
5
+ from typing import TYPE_CHECKING, Sequence, cast
6
6
  from warnings import warn
7
7
 
8
8
  import numpy as np
@@ -387,14 +387,15 @@ class scale_continuous(
387
387
  limits = self.final_limits
388
388
 
389
389
  x = self.oob(self.rescaler(x, _from=limits))
390
+ na_value = cast("float", self.na_value)
390
391
 
391
392
  uniq = np.unique(x)
392
393
  pal = np.asarray(self.palette(uniq))
393
394
  scaled = pal[match(x, uniq)]
394
395
  if scaled.dtype.kind == "U":
395
- scaled = [self.na_value if x == "nan" else x for x in scaled]
396
+ scaled = [na_value if x == "nan" else x for x in scaled]
396
397
  else:
397
- scaled[pd.isna(scaled)] = self.na_value
398
+ scaled[pd.isna(scaled)] = na_value
398
399
  return scaled
399
400
 
400
401
  def get_breaks(
@@ -520,11 +521,12 @@ class scale_continuous(
520
521
  # When user sets breaks and labels of equal size,
521
522
  # but the limits exclude some of the breaks.
522
523
  # We remove the corresponding labels
523
- from collections.abc import Sized
524
+ from collections.abc import Iterable, Sized
524
525
 
525
526
  labels = self.labels
526
527
  if (
527
528
  len(labels) != len(breaks)
529
+ and isinstance(self.breaks, Iterable)
528
530
  and isinstance(self.breaks, Sized)
529
531
  and len(labels) == len(self.breaks)
530
532
  ):
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from dataclasses import KW_ONLY, InitVar, dataclass
4
4
  from typing import TYPE_CHECKING
5
+ from warnings import warn
5
6
 
6
7
  from ._runtime_typing import TransUser # noqa: TCH001
7
8
  from .scale_continuous import scale_continuous
@@ -20,24 +21,21 @@ class scale_datetime(scale_continuous):
20
21
  """
21
22
  A string giving the distance between major breaks.
22
23
  For example `'2 weeks'`, `'5 years'`. If specified,
23
- `date_breaks` takes precedence over
24
- `breaks`.
24
+ `date_breaks` takes precedence over `breaks`.
25
25
  """
26
26
 
27
27
  date_labels: InitVar[str | None] = None
28
28
  """
29
29
  Format string for the labels.
30
30
  See [strftime](:ref:`strftime-strptime-behavior`).
31
- If specified, `date_labels` takes precedence over
32
- `labels`.
31
+ If specified, `date_labels` takes precedence over `labels`.
33
32
  """
34
33
 
35
34
  date_minor_breaks: InitVar[str | None] = None
36
35
  """
37
36
  A string giving the distance between minor breaks.
38
37
  For example `'2 weeks'`, `'5 years'`. If specified,
39
- `date_minor_breaks` takes precedence over
40
- `minor_breaks`.
38
+ `date_minor_breaks` takes precedence over `minor_breaks`.
41
39
  """
42
40
 
43
41
  _: KW_ONLY
@@ -80,22 +78,38 @@ class scale_datetime(scale_continuous):
80
78
  date_labels: str | None,
81
79
  date_minor_breaks: str | None,
82
80
  ):
83
- from mizani.breaks import breaks_date as breaks_func
84
- from mizani.labels import label_date as labels_func
81
+ from mizani.breaks import breaks_date_width
82
+ from mizani.labels import label_date
85
83
 
86
84
  if date_breaks is not None:
87
- self.breaks = breaks_func(date_breaks) # pyright: ignore
85
+ self.breaks = breaks_date_width(date_breaks) # pyright: ignore[reportAttributeAccessIssue]
88
86
  elif isinstance(self.breaks, str):
89
- self.breaks = breaks_func(width=self.breaks) # pyright: ignore
87
+ warn(
88
+ "Passing a string to `breaks` will not work in "
89
+ f"future versions. Use `date_breaks={self.breaks!r}`",
90
+ FutureWarning,
91
+ )
92
+ self.breaks = breaks_date_width(width=self.breaks) # pyright: ignore[reportAttributeAccessIssue]
90
93
 
91
94
  if date_labels is not None:
92
- self.labels = labels_func(date_labels) # pyright: ignore
95
+ self.labels = label_date(fmt=date_labels) # pyright: ignore[reportAttributeAccessIssue]
93
96
  elif isinstance(self.labels, str):
94
- self.labels = labels_func(width=self.labels) # pyright: ignore
97
+ warn(
98
+ "Passing a string to `labels` will not work in "
99
+ f"future versions. Use `date_labels={self.labels!r}`",
100
+ FutureWarning,
101
+ )
102
+ self.labels = label_date(fmt=self.labels) # pyright: ignore[reportAttributeAccessIssue]
95
103
 
96
104
  if date_minor_breaks is not None:
97
- self.minor_breaks = breaks_func(date_minor_breaks) # pyright: ignore
105
+ self.minor_breaks = breaks_date_width(date_minor_breaks) # pyright: ignore[reportAttributeAccessIssue]
98
106
  elif isinstance(self.minor_breaks, str):
99
- self.minor_breaks = breaks_func(width=self.minor_breaks) # pyright: ignore
107
+ warn(
108
+ "Passing a string to `minor_breaks` will not work in "
109
+ "future versions. "
110
+ f"Use `date_minor_breaks={self.minor_breaks!r}`",
111
+ FutureWarning,
112
+ )
113
+ self.minor_breaks = breaks_date_width(width=self.minor_breaks) # pyright: ignore[reportAttributeAccessIssue]
100
114
 
101
115
  scale_continuous.__post_init__(self)
@@ -156,7 +156,7 @@ class scale_discrete(
156
156
  range = self.dimension(limits=limits)
157
157
 
158
158
  breaks_d = self.get_breaks(limits)
159
- breaks = self.map(pd.Categorical(breaks_d))
159
+ breaks = self.map(pd.Categorical(breaks_d)) # pyright: ignore[reportArgumentType]
160
160
  minor_breaks = []
161
161
  labels = self.get_labels(breaks_d)
162
162
 
@@ -43,6 +43,8 @@ class scale_color_identity(MapTrainMixin, scale_discrete):
43
43
  """
44
44
 
45
45
  _aesthetics = ["color"]
46
+ _: KW_ONLY
47
+ guide: Literal["legend"] | None = None
46
48
 
47
49
 
48
50
  @dataclass
@@ -52,6 +54,8 @@ class scale_fill_identity(scale_color_identity):
52
54
  """
53
55
 
54
56
  _aesthetics = ["fill"]
57
+ _: KW_ONLY
58
+ guide: Literal["legend"] | None = None
55
59
 
56
60
 
57
61
  @dataclass
@@ -61,6 +65,8 @@ class scale_shape_identity(MapTrainMixin, scale_discrete):
61
65
  """
62
66
 
63
67
  _aesthetics = ["shape"]
68
+ _: KW_ONLY
69
+ guide: Literal["legend"] | None = None
64
70
 
65
71
 
66
72
  @dataclass
@@ -70,6 +76,8 @@ class scale_linetype_identity(MapTrainMixin, scale_discrete):
70
76
  """
71
77
 
72
78
  _aesthetics = ["linetype"]
79
+ _: KW_ONLY
80
+ guide: Literal["legend"] | None = None
73
81
 
74
82
 
75
83
  @dataclass
@@ -82,7 +90,7 @@ class scale_alpha_identity(
82
90
 
83
91
  _aesthetics = ["alpha"]
84
92
  _: KW_ONLY
85
- guide: Literal["legend"] | None = "legend"
93
+ guide: Literal["legend"] | None = None
86
94
 
87
95
 
88
96
  @dataclass
@@ -95,7 +103,18 @@ class scale_size_identity(
95
103
 
96
104
  _aesthetics = ["size"]
97
105
  _: KW_ONLY
98
- guide: Literal["legend"] | None = "legend"
106
+ guide: Literal["legend"] | None = None
107
+
108
+
109
+ @dataclass
110
+ class scale_stroke_identity(MapTrainMixin, scale_discrete):
111
+ """
112
+ No stroke scaling
113
+ """
114
+
115
+ _aesthetics = ["stroke"]
116
+ _: KW_ONLY
117
+ guide: Literal["legend"] | None = None
99
118
 
100
119
 
101
120
  # American to British spelling
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from collections.abc import Mapping
3
4
  from dataclasses import KW_ONLY, InitVar, dataclass
4
5
  from typing import Any, Sequence
5
6
  from warnings import warn
@@ -21,11 +22,16 @@ class _scale_manual(scale_discrete):
21
22
  """
22
23
 
23
24
  def __post_init__(self, values):
24
- from collections.abc import Sized
25
+ from collections.abc import Iterable, Sized
25
26
 
26
27
  super().__post_init__()
27
28
 
28
- if isinstance(self.breaks, Sized) and len(self.breaks) == len(values):
29
+ if (
30
+ isinstance(self.breaks, Iterable)
31
+ and isinstance(self.breaks, Sized)
32
+ and len(self.breaks) == len(values)
33
+ and not isinstance(values, Mapping)
34
+ ):
29
35
  values = dict(zip(self.breaks, values))
30
36
 
31
37
  def palette(n):
@@ -213,7 +213,7 @@ class scale_x_discrete(scale_position_discrete):
213
213
  Discrete x position
214
214
  """
215
215
 
216
- _aesthetics = ["x", "xmin", "xmax", "xend"]
216
+ _aesthetics = ["x", "xmin", "xmax", "xend", "xintercept"]
217
217
 
218
218
 
219
219
  @dataclass(kw_only=True)
@@ -222,7 +222,7 @@ class scale_y_discrete(scale_position_discrete):
222
222
  Discrete y position
223
223
  """
224
224
 
225
- _aesthetics = ["y", "ymin", "ymax", "yend"]
225
+ _aesthetics = ["y", "ymin", "ymax", "yend", "yintercept"]
226
226
 
227
227
 
228
228
  # Not part of the user API
plotnine/stats/binning.py CHANGED
@@ -165,7 +165,10 @@ def assign_bins(
165
165
  if weight is None:
166
166
  weight = np.ones(len(x))
167
167
  else:
168
- weight = np.asarray(weight)
168
+ # If weight is a dtype that isn't writeable
169
+ # and does not own it's memory. Using a list
170
+ # as an intermediate easily solves this.
171
+ weight = np.array(list(weight))
169
172
  weight[np.isnan(weight)] = 0
170
173
 
171
174
  bin_idx = pd.cut(
@@ -12,12 +12,9 @@ from ..exceptions import PlotnineError, PlotnineWarning
12
12
 
13
13
  if TYPE_CHECKING:
14
14
  import statsmodels.api as sm
15
- from patsy.eval import EvalEnvironment
16
15
 
17
- from plotnine.mapping import Environment
18
16
 
19
-
20
- def predictdf(data, xseq, **params) -> pd.DataFrame:
17
+ def predictdf(data, xseq, params) -> pd.DataFrame:
21
18
  """
22
19
  Make prediction on the data
23
20
 
@@ -49,21 +46,21 @@ def predictdf(data, xseq, **params) -> pd.DataFrame:
49
46
  if not callable(method):
50
47
  msg = (
51
48
  "'method' should either be a string or a function"
52
- "with the signature `func(data, xseq, **params)`"
49
+ "with the signature `func(data, xseq, params)`"
53
50
  )
54
51
  raise PlotnineError(msg)
55
52
 
56
- return method(data, xseq, **params)
53
+ return method(data, xseq, params)
57
54
 
58
55
 
59
- def lm(data, xseq, **params) -> pd.DataFrame:
56
+ def lm(data, xseq, params) -> pd.DataFrame:
60
57
  """
61
58
  Fit OLS / WLS if data has weight
62
59
  """
63
60
  import statsmodels.api as sm
64
61
 
65
62
  if params["formula"]:
66
- return lm_formula(data, xseq, **params)
63
+ return lm_formula(data, xseq, params)
67
64
 
68
65
  X = sm.add_constant(data["x"])
69
66
  Xseq = sm.add_constant(xseq)
@@ -96,14 +93,14 @@ def lm(data, xseq, **params) -> pd.DataFrame:
96
93
  return data
97
94
 
98
95
 
99
- def lm_formula(data, xseq, **params) -> pd.DataFrame:
96
+ def lm_formula(data, xseq, params) -> pd.DataFrame:
100
97
  """
101
98
  Fit OLS / WLS using a formula
102
99
  """
103
100
  import statsmodels.api as sm
104
101
  import statsmodels.formula.api as smf
105
102
 
106
- eval_env = _to_patsy_env(params["environment"])
103
+ eval_env = params["environment"].to_patsy_env()
107
104
  formula = params["formula"]
108
105
  weights = data.get("weight", None)
109
106
 
@@ -140,14 +137,14 @@ def lm_formula(data, xseq, **params) -> pd.DataFrame:
140
137
  return data
141
138
 
142
139
 
143
- def rlm(data, xseq, **params) -> pd.DataFrame:
140
+ def rlm(data, xseq, params) -> pd.DataFrame:
144
141
  """
145
142
  Fit RLM
146
143
  """
147
144
  import statsmodels.api as sm
148
145
 
149
146
  if params["formula"]:
150
- return rlm_formula(data, xseq, **params)
147
+ return rlm_formula(data, xseq, params)
151
148
 
152
149
  X = sm.add_constant(data["x"])
153
150
  Xseq = sm.add_constant(xseq)
@@ -170,14 +167,14 @@ def rlm(data, xseq, **params) -> pd.DataFrame:
170
167
  return data
171
168
 
172
169
 
173
- def rlm_formula(data, xseq, **params) -> pd.DataFrame:
170
+ def rlm_formula(data, xseq, params) -> pd.DataFrame:
174
171
  """
175
172
  Fit RLM using a formula
176
173
  """
177
174
  import statsmodels.api as sm
178
175
  import statsmodels.formula.api as smf
179
176
 
180
- eval_env = _to_patsy_env(params["environment"])
177
+ eval_env = params["environment"].to_patsy_env()
181
178
  formula = params["formula"]
182
179
  init_kwargs, fit_kwargs = separate_method_kwargs(
183
180
  params["method_args"], sm.RLM, sm.RLM.fit
@@ -196,14 +193,14 @@ def rlm_formula(data, xseq, **params) -> pd.DataFrame:
196
193
  return data
197
194
 
198
195
 
199
- def gls(data, xseq, **params) -> pd.DataFrame:
196
+ def gls(data, xseq, params) -> pd.DataFrame:
200
197
  """
201
198
  Fit GLS
202
199
  """
203
200
  import statsmodels.api as sm
204
201
 
205
202
  if params["formula"]:
206
- return gls_formula(data, xseq, **params)
203
+ return gls_formula(data, xseq, params)
207
204
 
208
205
  X = sm.add_constant(data["x"])
209
206
  Xseq = sm.add_constant(xseq)
@@ -227,14 +224,14 @@ def gls(data, xseq, **params) -> pd.DataFrame:
227
224
  return data
228
225
 
229
226
 
230
- def gls_formula(data, xseq, **params):
227
+ def gls_formula(data, xseq, params):
231
228
  """
232
229
  Fit GLL using a formula
233
230
  """
234
231
  import statsmodels.api as sm
235
232
  import statsmodels.formula.api as smf
236
233
 
237
- eval_env = _to_patsy_env(params["environment"])
234
+ eval_env = params["environment"].to_patsy_env()
238
235
  formula = params["formula"]
239
236
  init_kwargs, fit_kwargs = separate_method_kwargs(
240
237
  params["method_args"], sm.GLS, sm.GLS.fit
@@ -258,14 +255,14 @@ def gls_formula(data, xseq, **params):
258
255
  return data
259
256
 
260
257
 
261
- def glm(data, xseq, **params) -> pd.DataFrame:
258
+ def glm(data, xseq, params) -> pd.DataFrame:
262
259
  """
263
260
  Fit GLM
264
261
  """
265
262
  import statsmodels.api as sm
266
263
 
267
264
  if params["formula"]:
268
- return glm_formula(data, xseq, **params)
265
+ return glm_formula(data, xseq, params)
269
266
 
270
267
  X = sm.add_constant(data["x"])
271
268
  Xseq = sm.add_constant(xseq)
@@ -292,14 +289,14 @@ def glm(data, xseq, **params) -> pd.DataFrame:
292
289
  return data
293
290
 
294
291
 
295
- def glm_formula(data, xseq, **params):
292
+ def glm_formula(data, xseq, params):
296
293
  """
297
294
  Fit with GLM formula
298
295
  """
299
296
  import statsmodels.api as sm
300
297
  import statsmodels.formula.api as smf
301
298
 
302
- eval_env = _to_patsy_env(params["environment"])
299
+ eval_env = params["environment"].to_patsy_env()
303
300
  init_kwargs, fit_kwargs = separate_method_kwargs(
304
301
  params["method_args"], sm.GLM, sm.GLM.fit
305
302
  )
@@ -321,7 +318,7 @@ def glm_formula(data, xseq, **params):
321
318
  return data
322
319
 
323
320
 
324
- def lowess(data, xseq, **params) -> pd.DataFrame:
321
+ def lowess(data, xseq, params) -> pd.DataFrame:
325
322
  """
326
323
  Lowess fitting
327
324
  """
@@ -351,7 +348,7 @@ def lowess(data, xseq, **params) -> pd.DataFrame:
351
348
  return data
352
349
 
353
350
 
354
- def loess(data, xseq, **params) -> pd.DataFrame:
351
+ def loess(data, xseq, params) -> pd.DataFrame:
355
352
  """
356
353
  Loess smoothing
357
354
  """
@@ -402,7 +399,7 @@ def loess(data, xseq, **params) -> pd.DataFrame:
402
399
  return data
403
400
 
404
401
 
405
- def mavg(data, xseq, **params) -> pd.DataFrame:
402
+ def mavg(data, xseq, params) -> pd.DataFrame:
406
403
  """
407
404
  Fit moving average
408
405
  """
@@ -426,7 +423,7 @@ def mavg(data, xseq, **params) -> pd.DataFrame:
426
423
  return data
427
424
 
428
425
 
429
- def gpr(data, xseq, **params):
426
+ def gpr(data, xseq, params):
430
427
  """
431
428
  Fit gaussian process
432
429
  """
@@ -593,16 +590,6 @@ def separate_method_kwargs(method_args, init_method, fit_method):
593
590
  return init_kwargs, fit_kwargs
594
591
 
595
592
 
596
- def _to_patsy_env(environment: Environment) -> EvalEnvironment:
597
- """
598
- Convert a plotnine environment to a patsy environment
599
- """
600
- from patsy.eval import EvalEnvironment
601
-
602
- eval_env = EvalEnvironment(environment.namespaces)
603
- return eval_env
604
-
605
-
606
593
  def _glm_family(family: str) -> sm.families.Family:
607
594
  """
608
595
  Get glm-family instance