plotnine 0.15.0.dev2__py3-none-any.whl → 0.15.1__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 (140) 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 +128 -83
  4. plotnine/_mpl/layout_manager/_layout_tree.py +761 -310
  5. plotnine/_mpl/layout_manager/_spaces.py +320 -103
  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 +21 -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 +11 -2
  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 +8 -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 +28 -8
  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 +85 -0
  77. plotnine/mapping/aes.py +91 -72
  78. plotnine/mapping/evaluation.py +7 -65
  79. plotnine/scales/__init__.py +2 -0
  80. plotnine/scales/limits.py +7 -7
  81. plotnine/scales/scale.py +3 -3
  82. plotnine/scales/scale_color.py +82 -18
  83. plotnine/scales/scale_continuous.py +6 -4
  84. plotnine/scales/scale_datetime.py +28 -14
  85. plotnine/scales/scale_discrete.py +1 -1
  86. plotnine/scales/scale_identity.py +21 -2
  87. plotnine/scales/scale_manual.py +8 -2
  88. plotnine/scales/scale_xy.py +2 -2
  89. plotnine/stats/binning.py +4 -1
  90. plotnine/stats/smoothers.py +23 -36
  91. plotnine/stats/stat.py +20 -32
  92. plotnine/stats/stat_bin.py +6 -5
  93. plotnine/stats/stat_bin_2d.py +11 -9
  94. plotnine/stats/stat_bindot.py +13 -16
  95. plotnine/stats/stat_boxplot.py +6 -6
  96. plotnine/stats/stat_count.py +6 -9
  97. plotnine/stats/stat_density.py +7 -10
  98. plotnine/stats/stat_density_2d.py +12 -8
  99. plotnine/stats/stat_ecdf.py +7 -6
  100. plotnine/stats/stat_ellipse.py +9 -6
  101. plotnine/stats/stat_function.py +10 -8
  102. plotnine/stats/stat_hull.py +6 -3
  103. plotnine/stats/stat_identity.py +5 -2
  104. plotnine/stats/stat_pointdensity.py +5 -7
  105. plotnine/stats/stat_qq.py +46 -20
  106. plotnine/stats/stat_qq_line.py +16 -11
  107. plotnine/stats/stat_quantile.py +15 -9
  108. plotnine/stats/stat_sina.py +45 -14
  109. plotnine/stats/stat_smooth.py +8 -10
  110. plotnine/stats/stat_sum.py +5 -2
  111. plotnine/stats/stat_summary.py +7 -10
  112. plotnine/stats/stat_summary_bin.py +11 -14
  113. plotnine/stats/stat_unique.py +5 -2
  114. plotnine/stats/stat_ydensity.py +8 -11
  115. plotnine/themes/elements/__init__.py +2 -1
  116. plotnine/themes/elements/element_line.py +17 -9
  117. plotnine/themes/elements/margin.py +64 -1
  118. plotnine/themes/theme.py +9 -1
  119. plotnine/themes/theme_538.py +0 -1
  120. plotnine/themes/theme_bw.py +0 -1
  121. plotnine/themes/theme_dark.py +0 -1
  122. plotnine/themes/theme_gray.py +6 -5
  123. plotnine/themes/theme_light.py +1 -1
  124. plotnine/themes/theme_matplotlib.py +5 -5
  125. plotnine/themes/theme_seaborn.py +7 -4
  126. plotnine/themes/theme_void.py +9 -8
  127. plotnine/themes/theme_xkcd.py +0 -1
  128. plotnine/themes/themeable.py +110 -32
  129. plotnine/typing.py +17 -6
  130. plotnine/watermark.py +3 -3
  131. {plotnine-0.15.0.dev2.dist-info → plotnine-0.15.1.dist-info}/METADATA +13 -6
  132. plotnine-0.15.1.dist-info/RECORD +221 -0
  133. {plotnine-0.15.0.dev2.dist-info → plotnine-0.15.1.dist-info}/WHEEL +1 -1
  134. plotnine/plot_composition/__init__.py +0 -10
  135. plotnine/plot_composition/_compose.py +0 -436
  136. plotnine/plot_composition/_spacer.py +0 -32
  137. plotnine-0.15.0.dev2.dist-info/RECORD +0 -214
  138. /plotnine/{plot_composition → composition}/_plotspec.py +0 -0
  139. {plotnine-0.15.0.dev2.dist-info → plotnine-0.15.1.dist-info}/licenses/LICENSE +0 -0
  140. {plotnine-0.15.0.dev2.dist-info → plotnine-0.15.1.dist-info}/top_level.txt +0 -0
@@ -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
plotnine/stats/stat.py CHANGED
@@ -64,7 +64,7 @@ class stat(ABC, metaclass=Register):
64
64
 
65
65
  # Plot namespace, it gets its value when the plot is being
66
66
  # built.
67
- environment: Environment | None = None
67
+ environment: Environment
68
68
 
69
69
  def __init__(
70
70
  self,
@@ -195,9 +195,9 @@ class stat(ABC, metaclass=Register):
195
195
 
196
196
  return data
197
197
 
198
- def setup_params(self, data: pd.DataFrame) -> dict[str, Any]:
198
+ def setup_params(self, data: pd.DataFrame):
199
199
  """
200
- Override this to verify or adjust parameters
200
+ Override this to verify and/or adjust parameters
201
201
 
202
202
  Parameters
203
203
  ----------
@@ -209,7 +209,6 @@ class stat(ABC, metaclass=Register):
209
209
  out :
210
210
  Parameters used by the stats.
211
211
  """
212
- return self.params
213
212
 
214
213
  def setup_data(self, data: pd.DataFrame) -> pd.DataFrame:
215
214
  """
@@ -227,9 +226,7 @@ class stat(ABC, metaclass=Register):
227
226
  """
228
227
  return data
229
228
 
230
- def finish_layer(
231
- self, data: pd.DataFrame, params: dict[str, Any]
232
- ) -> pd.DataFrame:
229
+ def finish_layer(self, data: pd.DataFrame) -> pd.DataFrame:
233
230
  """
234
231
  Modify data after the aesthetics have been mapped
235
232
 
@@ -257,9 +254,8 @@ class stat(ABC, metaclass=Register):
257
254
  """
258
255
  return data
259
256
 
260
- @classmethod
261
257
  def compute_layer(
262
- cls, data: pd.DataFrame, params: dict[str, Any], layout: Layout
258
+ self, data: pd.DataFrame, layout: Layout
263
259
  ) -> pd.DataFrame:
264
260
  """
265
261
  Calculate statistics for this layers
@@ -275,22 +271,20 @@ class stat(ABC, metaclass=Register):
275
271
  ----------
276
272
  data :
277
273
  Data points for all objects in a layer.
278
- params :
279
- Stat parameters
280
274
  layout :
281
275
  Panel layout information
282
276
  """
283
277
  check_required_aesthetics(
284
- cls.REQUIRED_AES,
285
- list(data.columns) + list(params.keys()),
286
- cls.__name__,
278
+ self.REQUIRED_AES,
279
+ list(data.columns) + list(self.params.keys()),
280
+ self.__class__.__name__,
287
281
  )
288
282
 
289
283
  data = remove_missing(
290
284
  data,
291
- na_rm=params.get("na_rm", False),
292
- vars=list(cls.REQUIRED_AES | cls.NON_MISSING_AES),
293
- name=cls.__name__,
285
+ na_rm=self.params.get("na_rm", False),
286
+ vars=list(self.REQUIRED_AES | self.NON_MISSING_AES),
287
+ name=self.__class__.__name__,
294
288
  finite=True,
295
289
  )
296
290
 
@@ -304,14 +298,11 @@ class stat(ABC, metaclass=Register):
304
298
  if len(pdata) == 0:
305
299
  return pdata
306
300
  pscales = layout.get_scales(pdata["PANEL"].iloc[0])
307
- return cls.compute_panel(pdata, pscales, **params)
301
+ return self.compute_panel(pdata, pscales)
308
302
 
309
303
  return groupby_apply(data, "PANEL", fn)
310
304
 
311
- @classmethod
312
- def compute_panel(
313
- cls, data: pd.DataFrame, scales: pos_scales, **params: Any
314
- ):
305
+ def compute_panel(self, data: pd.DataFrame, scales: pos_scales):
315
306
  """
316
307
  Calculate the statistics for all the groups
317
308
 
@@ -341,14 +332,12 @@ class stat(ABC, metaclass=Register):
341
332
 
342
333
  stats = []
343
334
  for _, old in data.groupby("group"):
344
- new = cls.compute_group(old, scales, **params)
335
+ new = self.compute_group(old, scales)
345
336
  new.reset_index(drop=True, inplace=True)
346
337
  unique = uniquecols(old)
347
338
  missing = unique.columns.difference(new.columns)
348
339
  idx = [0] * len(new)
349
- u = unique.loc[idx, missing].reset_index( # pyright: ignore
350
- drop=True
351
- )
340
+ u = unique.loc[idx, missing].reset_index(drop=True)
352
341
  # concat can have problems with empty dataframes that
353
342
  # have an index
354
343
  if u.empty and len(u):
@@ -365,9 +354,8 @@ class stat(ABC, metaclass=Register):
365
354
  # it completely.
366
355
  return stats
367
356
 
368
- @classmethod
369
357
  def compute_group(
370
- cls, data: pd.DataFrame, scales: pos_scales, **params: Any
358
+ self, data: pd.DataFrame, scales: pos_scales
371
359
  ) -> pd.DataFrame:
372
360
  """
373
361
  Calculate statistics for the group
@@ -390,9 +378,9 @@ class stat(ABC, metaclass=Register):
390
378
  Parameters
391
379
  """
392
380
  msg = "{} should implement this method."
393
- raise NotImplementedError(msg.format(cls.__name__))
381
+ raise NotImplementedError(msg.format(self.__class__.__name__))
394
382
 
395
- def __radd__(self, plot: ggplot) -> ggplot:
383
+ def __radd__(self, other: ggplot) -> ggplot:
396
384
  """
397
385
  Add layer representing stat object on the right
398
386
 
@@ -406,8 +394,8 @@ class stat(ABC, metaclass=Register):
406
394
  out :
407
395
  ggplot object with added layer
408
396
  """
409
- plot += self.to_layer() # Add layer
410
- return plot
397
+ other += self.to_layer() # Add layer
398
+ return other
411
399
 
412
400
  def to_layer(self) -> layer:
413
401
  """
@@ -53,6 +53,10 @@ class stat_bin(stat):
53
53
  pad : bool, default=False
54
54
  If `True`{.py}, adds empty bins at either side of x.
55
55
  This ensures that frequency polygons touch 0.
56
+
57
+ See Also
58
+ --------
59
+ plotnine.histogram : The default `geom` for this `stat`.
56
60
  """
57
61
 
58
62
  _aesthetics_doc = """
@@ -100,7 +104,6 @@ class stat_bin(stat):
100
104
  and params["binwidth"] is None
101
105
  and params["bins"] is None
102
106
  ):
103
- params = params.copy()
104
107
  params["bins"] = freedman_diaconis_bins(data["x"])
105
108
  msg = (
106
109
  "'stat_bin()' using 'bins = {}'. "
@@ -108,10 +111,8 @@ class stat_bin(stat):
108
111
  )
109
112
  warn(msg.format(params["bins"]), PlotnineWarning)
110
113
 
111
- return params
112
-
113
- @classmethod
114
- def compute_group(cls, data, scales, **params):
114
+ def compute_group(self, data, scales):
115
+ params = self.params
115
116
  if params["breaks"] is not None:
116
117
  breaks = np.asarray(params["breaks"])
117
118
  if hasattr(scales.x, "transform"):
@@ -35,6 +35,10 @@ class stat_bin_2d(stat):
35
35
  the stories in your data.
36
36
  drop : bool, default=False
37
37
  If `True`{.py}, removes all cells with zero counts.
38
+
39
+ See Also
40
+ --------
41
+ plotnine.geom_rect : The default `geom` for this `stat`.
38
42
  """
39
43
 
40
44
  _aesthetics_doc = """
@@ -66,18 +70,16 @@ class stat_bin_2d(stat):
66
70
  CREATES = {"xmin", "xmax", "ymin", "ymax", "count", "density"}
67
71
 
68
72
  def setup_params(self, data):
69
- params = self.params.copy()
73
+ params = self.params
70
74
  params["bins"] = dual_param(params["bins"])
71
75
  params["breaks"] = dual_param(params["breaks"])
72
76
  params["binwidth"] = dual_param(params["binwidth"])
73
- return params
74
-
75
- @classmethod
76
- def compute_group(cls, data, scales, **params):
77
- bins = params["bins"]
78
- breaks = params["breaks"]
79
- binwidth = params["binwidth"]
80
- drop = params["drop"]
77
+
78
+ def compute_group(self, data, scales):
79
+ bins = self.params["bins"]
80
+ breaks = self.params["breaks"]
81
+ binwidth = self.params["binwidth"]
82
+ drop = self.params["drop"]
81
83
  weight = data.get("weight")
82
84
 
83
85
  if weight is None:
@@ -68,6 +68,7 @@ class stat_bindot(stat):
68
68
 
69
69
  See Also
70
70
  --------
71
+ plotnine.geom_dotplot : The default `geom` for this `stat`.
71
72
  plotnine.stat_bin
72
73
  """
73
74
 
@@ -113,18 +114,16 @@ class stat_bindot(stat):
113
114
  and params["binwidth"] is None
114
115
  and params["bins"] is None
115
116
  ):
116
- params = params.copy()
117
- params["bins"] = freedman_diaconis_bins(data["x"])
118
- msg = (
119
- "'stat_bin()' using 'bins = {}'. "
120
- "Pick better value with 'binwidth'."
117
+ bins = freedman_diaconis_bins(data["x"])
118
+ params["bins"] = bins
119
+ warn(
120
+ f"'stat_bindot' is using '{bins=}'. "
121
+ "Pick better value with 'binwidth'",
122
+ PlotnineWarning,
121
123
  )
122
- warn(msg.format(params["bins"]), PlotnineWarning)
123
124
 
124
- return params
125
-
126
- @classmethod
127
- def compute_panel(cls, data, scales, **params):
125
+ def compute_panel(self, data, scales):
126
+ params = self.params
128
127
  if (
129
128
  params["method"] == "dotdensity"
130
129
  and params["binpositions"] == "all"
@@ -160,10 +159,10 @@ class stat_bindot(stat):
160
159
  data["binwidth"] = newdata["binwidth"]
161
160
  data["weight"] = newdata["weight"]
162
161
  data["bincenter"] = newdata["bincenter"]
163
- return super(cls, stat_bindot).compute_panel(data, scales, **params)
162
+ return super().compute_panel(data, scales)
164
163
 
165
- @classmethod
166
- def compute_group(cls, data, scales, **params):
164
+ def compute_group(self, data, scales):
165
+ params = self.params
167
166
  # Check that weights are whole numbers
168
167
  # (for dots, weights must be whole)
169
168
  weight = data.get("weight")
@@ -281,9 +280,7 @@ def densitybin(
281
280
  if all(pd.isna(x)):
282
281
  return pd.DataFrame()
283
282
 
284
- if weight is None:
285
- weight = np.ones(len(x))
286
- weight = np.asarray(weight)
283
+ weight = np.ones(len(x)) if weight is None else np.array(list(weight))
287
284
  weight[np.isnan(weight)] = 0
288
285
 
289
286
  if rangee is None:
@@ -22,7 +22,7 @@ class stat_boxplot(stat):
22
22
 
23
23
  See Also
24
24
  --------
25
- plotnine.geom_boxplot
25
+ plotnine.geom_boxplot: The default `geom` for this `stat`.
26
26
  """
27
27
 
28
28
  _aesthetics_doc = """
@@ -91,10 +91,8 @@ class stat_boxplot(stat):
91
91
  if self.params["width"] is None:
92
92
  x = data.get("x", 0)
93
93
  self.params["width"] = resolution(x, False) * 0.75
94
- return self.params
95
94
 
96
- @classmethod
97
- def compute_group(cls, data, scales, **params):
95
+ def compute_group(self, data, scales):
98
96
  n = len(data)
99
97
  y = data["y"].to_numpy()
100
98
  if "weight" in data:
@@ -103,12 +101,14 @@ class stat_boxplot(stat):
103
101
  else:
104
102
  weights = None
105
103
  total_weight = len(y)
106
- res = weighted_boxplot_stats(y, weights=weights, whis=params["coef"])
104
+ res = weighted_boxplot_stats(
105
+ y, weights=weights, whis=self.params["coef"]
106
+ )
107
107
 
108
108
  if len(np.unique(data["x"])) > 1:
109
109
  width = np.ptp(data["x"]) * 0.9
110
110
  else:
111
- width = params["width"]
111
+ width = self.params["width"]
112
112
 
113
113
  if isinstance(data["x"].dtype, pd.CategoricalDtype):
114
114
  x = data["x"].iloc[0]
@@ -23,6 +23,7 @@ class stat_count(stat):
23
23
 
24
24
  See Also
25
25
  --------
26
+ plotnine.geom_histogram : The default `geom` for this `stat`.
26
27
  plotnine.stat_bin
27
28
  """
28
29
 
@@ -49,21 +50,17 @@ class stat_count(stat):
49
50
  CREATES = {"count", "prop"}
50
51
 
51
52
  def setup_params(self, data):
52
- params = self.params.copy()
53
- if params["width"] is None:
54
- params["width"] = resolution(data["x"], False) * 0.9
53
+ if self.params["width"] is None:
54
+ self.params["width"] = resolution(data["x"], False) * 0.9
55
55
 
56
- return params
57
-
58
- @classmethod
59
- def compute_group(cls, data, scales, **params):
56
+ def compute_group(self, data, scales):
60
57
  x = data["x"]
61
- if ("y" in data) or ("y" in params):
58
+ if ("y" in data) or ("y" in self.params):
62
59
  msg = "stat_count() must not be used with a y aesthetic"
63
60
  raise PlotnineError(msg)
64
61
 
65
62
  weight = data.get("weight", [1] * len(x))
66
- width = params["width"]
63
+ width = self.params["width"]
67
64
  xdata_long = pd.DataFrame({"x": x, "weight": weight})
68
65
  # weighted frequency count
69
66
  count = xdata_long.pivot_table("weight", index=["x"], aggfunc="sum")[
@@ -85,7 +85,7 @@ class stat_density(stat):
85
85
 
86
86
  See Also
87
87
  --------
88
- plotnine.geom_density
88
+ plotnine.geom_density : The default `geom` for this `stat`.
89
89
  statsmodels.nonparametric.kde.KDEUnivariate
90
90
  statsmodels.nonparametric.kde.KDEUnivariate.fit
91
91
  """
@@ -102,9 +102,9 @@ class stat_density(stat):
102
102
  # useful for stacked density plots
103
103
 
104
104
  'scaled' # density estimate, scaled to maximum of 1
105
+ 'n' # Number of observations at a position
105
106
  ```
106
107
 
107
- 'n' # Number of observations at a position
108
108
 
109
109
  """
110
110
  REQUIRED_AES = {"x"}
@@ -126,7 +126,7 @@ class stat_density(stat):
126
126
  CREATES = {"density", "count", "scaled", "n"}
127
127
 
128
128
  def setup_params(self, data):
129
- params = self.params.copy()
129
+ params = self.params
130
130
  lookup = {
131
131
  "biweight": "biw",
132
132
  "cosine": "cos",
@@ -148,21 +148,18 @@ class stat_density(stat):
148
148
  )
149
149
  raise PlotnineError(msg)
150
150
 
151
- return params
152
-
153
- @classmethod
154
- def compute_group(cls, data, scales, **params):
151
+ def compute_group(self, data, scales):
155
152
  weight = data.get("weight")
156
153
 
157
- if params["trim"]:
154
+ if self.params["trim"]:
158
155
  range_x = data["x"].min(), data["x"].max()
159
156
  else:
160
157
  range_x = scales.x.dimension()
161
158
 
162
- return compute_density(data["x"], weight, range_x, **params)
159
+ return compute_density(data["x"], weight, range_x, self.params)
163
160
 
164
161
 
165
- def compute_density(x, weight, range, **params):
162
+ def compute_density(x, weight, range, params):
166
163
  """
167
164
  Compute density
168
165
  """
@@ -1,12 +1,17 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
1
5
  import numpy as np
2
6
  import pandas as pd
3
7
 
4
- from ..doctools import document
5
8
  from .density import get_var_type, kde
6
9
  from .stat import stat
7
10
 
11
+ if TYPE_CHECKING:
12
+ from plotnine.typing import FloatArrayLike
13
+
8
14
 
9
- @document
10
15
  class stat_density_2d(stat):
11
16
  """
12
17
  Compute 2D kernel density estimation
@@ -32,6 +37,7 @@ class stat_density_2d(stat):
32
37
 
33
38
  See Also
34
39
  --------
40
+ plotnine.geom_density_2d : The default `geom` for this `stat`.
35
41
  statsmodels.nonparametric.kernel_density.KDEMultivariate
36
42
  scipy.stats.gaussian_kde
37
43
  sklearn.neighbors.KernelDensity
@@ -66,7 +72,7 @@ class stat_density_2d(stat):
66
72
  CREATES = {"y"}
67
73
 
68
74
  def setup_params(self, data):
69
- params = self.params.copy()
75
+ params = self.params
70
76
  if params["kde_params"] is None:
71
77
  params["kde_params"] = {}
72
78
 
@@ -78,10 +84,8 @@ class stat_density_2d(stat):
78
84
  y_type = get_var_type(data["y"])
79
85
  kde_params["var_type"] = f"{x_type}{y_type}"
80
86
 
81
- return params
82
-
83
- @classmethod
84
- def compute_group(cls, data, scales, **params):
87
+ def compute_group(self, data, scales):
88
+ params = self.params
85
89
  package = params["package"]
86
90
  kde_params = params["kde_params"]
87
91
 
@@ -118,7 +122,7 @@ class stat_density_2d(stat):
118
122
  return data
119
123
 
120
124
 
121
- def contour_lines(X, Y, Z, levels):
125
+ def contour_lines(X, Y, Z, levels: int | FloatArrayLike):
122
126
  """
123
127
  Calculate contour lines
124
128
  """