growthcharts 0.1.0__py3-none-any.whl → 0.2.0__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.
growthcharts/child.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import pandas as pd
2
2
  import matplotlib.pyplot as plt
3
3
  import math
4
+ import json
4
5
  from scipy.stats import norm
5
6
 
6
7
  from .patient import Patient
@@ -49,221 +50,380 @@ class Child(Patient):
49
50
 
50
51
  super().__init__(sex, age, wt, length)
51
52
 
52
- def plot_wtage(self, title=None):
53
+ def plot_wtage(self, title=None, figsize=(12,7), show=True, ax=None):
53
54
  """
54
- Plot weight-for-age growth chart with patient's measurement.
55
+ Plot weight-for-age growth chart for the child.
56
+
57
+ This method draws CDC percentile curves and overlays
58
+ the patient's weight measurement.
59
+
60
+ The returned figure can be customized, embedded in apps,
61
+ or saved as an image.
62
+
63
+ Args:
64
+ title (str, optional):
65
+ Custom title for the chart.
66
+
67
+ figsize (tuple, optional):
68
+ Figure size in inches (width, height).
69
+ Default is (12, 7).
70
+
71
+ show (bool, optional):
72
+ Whether to display the plot using matplotlib.
73
+ If False, the plot will not be shown.
74
+ Default is True.
75
+
76
+ ax (matplotlib.axes.Axes, optional):
77
+ Existing matplotlib Axes object for embedding
78
+ in custom layouts or applications.
79
+
80
+ Returns:
81
+ tuple:
82
+ (fig, ax)
83
+
84
+ fig : matplotlib.figure.Figure
85
+ The created matplotlib figure.
86
+
87
+ ax : matplotlib.axes.Axes
88
+ The axes containing the plot.
89
+ """
55
90
 
56
- Args:
57
- title (str, optional): Plot title.
58
- """
59
91
  df = self.df_wtage
92
+ sex = self._normalize_sex()
60
93
 
61
- self.sex = str(self.sex).lower()
62
- if self.sex in ['m', '1']:
63
- self.sex = '1'
64
- elif self.sex in ['f', '2']:
65
- self.sex = '2'
66
- else:
67
- raise TypeError('define sex as m or f')
94
+ data = df[df['Sex'] == sex]
68
95
 
69
- data = df[df['Sex'] == self.sex]
96
+ if ax is None:
97
+ fig, ax = plt.subplots(figsize=figsize)
98
+ else:
99
+ fig = ax.figure
70
100
 
71
- plt.figure(figsize=(12, 7))
72
101
  for i, col in enumerate(self.agecolumns[1:]):
102
+
73
103
  if i in self.highlight_indices:
74
- plt.plot(data['Agemos'], data[col],
75
- color='gray', linewidth=2, label=f'{title} {col}')
104
+ ax.plot(
105
+ data['Agemos'], data[col],
106
+ color='gray', linewidth=2,
107
+ label=col
108
+ )
76
109
  else:
77
- plt.plot(data['Agemos'], data[col],
78
- color='lightgray', linewidth=1)
110
+ ax.plot(
111
+ data['Agemos'], data[col],
112
+ color='lightgray', linewidth=1
113
+ )
79
114
 
80
115
  if self.wt is not None:
81
- plt.scatter(self.age, self.wt, color='red', s=100, zorder=5)
116
+ ax.scatter(
117
+ self.age, self.wt,
118
+ color='red', s=100,
119
+ zorder=5, label="Patient"
120
+ )
82
121
 
83
- plt.title(title)
84
- plt.xlabel("age(month)")
85
- plt.ylabel("Weight(Kg)")
86
- plt.grid(True)
87
- plt.legend()
88
- plt.show()
122
+ ax.set_title(title or "Weight for Age")
123
+ ax.set_xlabel("Age (months)")
124
+ ax.set_ylabel("Weight (Kg)")
125
+ ax.grid(True)
126
+ ax.legend()
89
127
 
90
- def plot_lenage(self, title=None):
91
- """
92
- Plot length-for-age growth chart with patient's measurement.
128
+ if show:
129
+ plt.show()
93
130
 
94
- Args:
95
- title (str, optional): Plot title.
131
+ return fig, ax
132
+
133
+
134
+ def plot_lenage(self, title=None, figsize=(12,7), show=True, ax=None):
96
135
  """
136
+ Plot length/height-for-age growth chart for the child.
137
+
138
+ This method draws CDC percentile curves and overlays
139
+ the patient's length or height measurement.
140
+
141
+ The output figure can be customized, embedded in
142
+ applications, or exported as an image file.
143
+
144
+ Args:
145
+ title (str, optional):
146
+ Custom title for the chart.
147
+
148
+ figsize (tuple, optional):
149
+ Figure size in inches (width, height).
150
+ Default is (12, 7).
151
+
152
+ show (bool, optional):
153
+ Whether to display the plot using matplotlib.
154
+ If False, the plot will not be shown.
155
+ Default is True.
156
+
157
+ ax (matplotlib.axes.Axes, optional):
158
+ Existing matplotlib Axes object for embedding
159
+ in custom layouts or applications.
160
+
161
+ Returns:
162
+ tuple:
163
+ (fig, ax)
164
+
165
+ fig : matplotlib.figure.Figure
166
+ The created matplotlib figure.
167
+
168
+ ax : matplotlib.axes.Axes
169
+ The axes containing the plot.
170
+ """
171
+
97
172
  df = self.df_lenage
173
+ sex = self._normalize_sex()
98
174
 
99
- self.sex = str(self.sex).lower()
100
- if self.sex in ['m', '1']:
101
- self.sex = '1'
102
- elif self.sex in ['f', '2']:
103
- self.sex = '2'
104
- else:
105
- raise TypeError('define sex as m or f')
175
+ data = df[df['Sex'] == sex]
106
176
 
107
- data = df[df['Sex'] == self.sex]
177
+ if ax is None:
178
+ fig, ax = plt.subplots(figsize=figsize)
179
+ else:
180
+ fig = ax.figure
108
181
 
109
- plt.figure(figsize=(14, 10))
110
182
  for i, col in enumerate(self.agecolumns[1:]):
183
+
111
184
  if i in self.highlight_indices:
112
- plt.plot(data['Agemos'], data[col],
113
- color='gray', linewidth=2, label=f'{title} {col}')
185
+ ax.plot(
186
+ data['Agemos'], data[col],
187
+ color='gray', linewidth=2,
188
+ label=col
189
+ )
114
190
  else:
115
- plt.plot(data['Agemos'], data[col],
116
- color='lightgray', linewidth=1)
191
+ ax.plot(
192
+ data['Agemos'], data[col],
193
+ color='lightgray', linewidth=1
194
+ )
117
195
 
118
196
  if self.length is not None:
119
- plt.scatter(self.age, self.length, color='red', s=100, zorder=5)
197
+ ax.scatter(
198
+ self.age, self.length,
199
+ color='red', s=100,
200
+ zorder=5, label="Patient"
201
+ )
120
202
 
121
- plt.title(title)
122
- plt.xlabel("age(month)")
123
- plt.ylabel("Length(cm)")
124
- plt.grid(True)
125
- plt.legend()
126
- plt.show()
203
+ ax.set_title(title or "Length for Age")
204
+ ax.set_xlabel("Age (months)")
205
+ ax.set_ylabel("Length (cm)")
206
+ ax.grid(True)
207
+ ax.legend()
127
208
 
128
- def plot_bmiage(self, title=None):
129
- """
130
- Plot BMI-for-age growth chart with patient's measurement.
209
+ if show:
210
+ plt.show()
131
211
 
132
- Args:
133
- title (str, optional): Plot title.
212
+ return fig, ax
213
+
214
+
215
+ def plot_bmiage(self, title=None, figsize=(12,7), show=True, ax=None):
134
216
  """
217
+ Plot BMI-for-age growth chart for the child.
218
+
219
+ This method draws CDC BMI percentile curves and overlays
220
+ the patient's calculated BMI value.
221
+
222
+ The resulting figure can be modified, embedded in apps,
223
+ or saved to disk.
224
+
225
+ Args:
226
+ title (str, optional):
227
+ Custom title for the chart.
228
+
229
+ figsize (tuple, optional):
230
+ Figure size in inches (width, height).
231
+ Default is (12, 7).
232
+
233
+ show (bool, optional):
234
+ Whether to display the plot using matplotlib.
235
+ If False, the plot will not be shown.
236
+ Default is True.
237
+
238
+ ax (matplotlib.axes.Axes, optional):
239
+ Existing matplotlib Axes object for embedding
240
+ in custom layouts or applications.
241
+
242
+ Returns:
243
+ tuple:
244
+ (fig, ax)
245
+
246
+ fig : matplotlib.figure.Figure
247
+ The created matplotlib figure.
248
+
249
+ ax : matplotlib.axes.Axes
250
+ The axes containing the plot.
251
+ """
252
+
135
253
  df = self.df_bmiage
254
+ sex = self._normalize_sex()
136
255
 
137
- self.sex = str(self.sex).lower()
138
- if self.sex in ['m', '1']:
139
- self.sex = '1'
140
- elif self.sex in ['f', '2']:
141
- self.sex = '2'
142
- else:
143
- raise TypeError('define sex as m or f')
256
+ data = df[df['Sex'] == sex]
144
257
 
145
- data = df[df['Sex'] == self.sex]
258
+ if ax is None:
259
+ fig, ax = plt.subplots(figsize=figsize)
260
+ else:
261
+ fig = ax.figure
146
262
 
147
- plt.figure(figsize=(12, 7))
148
263
  for i, col in enumerate(self.agecolumns[1:]):
264
+
149
265
  if i in self.highlight_indices:
150
- plt.plot(data['Agemos'], data[col],
151
- color='gray', linewidth=2, label=f'{title} {col}')
266
+ ax.plot(
267
+ data['Agemos'], data[col],
268
+ color='gray', linewidth=2,
269
+ label=col
270
+ )
152
271
  else:
153
- plt.plot(data['Agemos'], data[col],
154
- color='lightgray', linewidth=1)
272
+ ax.plot(
273
+ data['Agemos'], data[col],
274
+ color='lightgray', linewidth=1
275
+ )
155
276
 
156
- if self.wt is not None and self.length is not None and self.length > 0:
157
- plt.scatter(self.age, self.bmi, color='red', s=100, zorder=5)
277
+ if self.wt and self.length and self.length > 0:
278
+ ax.scatter(
279
+ self.age, self.bmi,
280
+ color='red', s=100,
281
+ zorder=5, label="Patient"
282
+ )
158
283
 
159
- plt.title(title)
160
- plt.xlabel("age(month)")
161
- plt.ylabel("BMI")
162
- plt.grid(True)
163
- plt.legend()
164
- plt.show()
284
+ ax.set_title(title or "BMI for Age")
285
+ ax.set_xlabel("Age (months)")
286
+ ax.set_ylabel("BMI")
287
+ ax.grid(True)
288
+ ax.legend()
165
289
 
166
- def analyze_wtage(self):
167
- """
168
- Analyze weight-for-age using LMS method.
290
+ if show:
291
+ plt.show()
169
292
 
170
- Returns:
171
- dict: Z-score and percentile.
172
- """
173
- df = self.df_wtage
174
- self.sex = str(self.sex).lower()
175
- if self.sex in ['m', '1']:
176
- self.sex = '1'
177
- elif self.sex in ['f', '2']:
178
- self.sex = '2'
179
- else:
180
- raise TypeError('define sex as m or f')
293
+ return fig, ax
181
294
 
182
- df2 = df[df['Sex'] == self.sex]
183
- row = df2.iloc[(df2['Agemos'] - self.age).abs().argsort()[:1]]
184
295
 
185
- L = float(row['L'].values[0])
186
- M = float(row['M'].values[0])
187
- S = float(row['S'].values[0])
296
+ def analyze_wtage(self, return_json=False):
297
+ """
298
+ Analyze weight-for-age using LMS method.
188
299
 
189
- if L != 0:
190
- z = ((self.wt / M) ** L - 1) / (L * S)
191
- else:
192
- z = math.log(self.wt / M) / S
300
+ Args:
301
+ return_json (bool): If True, returns a JSON string. Default False.
193
302
 
194
- percentile = norm.cdf(z) * 100
303
+ Returns:
304
+ dict or JSON: Z-score, percentile, and interpretation.
305
+ """
306
+ df = self.df_wtage
307
+ sex = self._normalize_sex()
308
+ df2 = df[df['Sex'] == sex]
195
309
 
196
- return {
197
- "Z": round(z, 2),
198
- "Percentile": round(percentile, 2)
199
- }
310
+ row = df2.iloc[(df2['Agemos'] - self.age).abs().argsort()[:1]]
311
+ L, M, S = float(row['L'].values[0]), float(row['M'].values[0]), float(row['S'].values[0])
200
312
 
201
- def analyze_lenage(self):
202
- """
203
- Analyze length-for-age using LMS method.
313
+ if L != 0:
314
+ z = ((self.wt / M) ** L - 1) / (L * S)
315
+ else:
316
+ z = math.log(self.wt / M) / S
204
317
 
205
- Returns:
206
- dict: Z-score and percentile.
207
- """
208
- df = self.df_lenage
209
- self.sex = str(self.sex).lower()
210
- if self.sex in ['m', '1']:
211
- self.sex = '1'
212
- elif self.sex in ['f', '2']:
213
- self.sex = '2'
214
- else:
215
- raise TypeError('define sex as m or f')
318
+ percentile = norm.cdf(z) * 100
216
319
 
217
- df2 = df[df['Sex'] == self.sex]
218
- row = df2.iloc[(df2['Agemos'] - self.age).abs().argsort()[:1]]
320
+ result = {
321
+ "indicator": "Weight-for-Age",
322
+ "value": round(self.wt, 2),
323
+ "unit": None,
324
+ "Age_Months": round(self.age, 2),
325
+ "sex": "Male" if sex == "1" else "Female",
219
326
 
220
- L = float(row['L'].values[0])
221
- M = float(row['M'].values[0])
222
- S = float(row['S'].values[0])
327
+ "z_score": round(z, 2),
328
+ "percentile": round(percentile, 1),
223
329
 
224
- if L != 0:
225
- z = ((self.length / M) ** L - 1) / (L * S)
226
- else:
227
- z = math.log(self.length / M) / S
330
+ "z_interpretation": self._interpret_z(z),
331
+ "percentile_interpretation": self._interpret_percentile(percentile)
332
+ }
228
333
 
229
- percentile = norm.cdf(z) * 100
334
+ if return_json:
335
+ import json
336
+ return json.dumps(result)
230
337
 
231
- return {
232
- "Z": round(z, 2),
233
- "Percentile": round(percentile, 2)
234
- }
338
+ return result
235
339
 
236
- def analyze_bmiage(self):
237
- """
238
- Analyze BMI-for-age using LMS method.
239
340
 
240
- Returns:
241
- dict: Z-score and percentile.
242
- """
243
- df = self.df_bmiage
244
- self.sex = str(self.sex).lower()
245
- if self.sex in ['m', '1']:
246
- self.sex = '1'
247
- elif self.sex in ['f', '2']:
248
- self.sex = '2'
249
- else:
250
- raise TypeError('define sex as m or f')
341
+ def analyze_lenage(self, return_json=False):
342
+ """
343
+ Analyze length-for-age using LMS method.
251
344
 
252
- df2 = df[df['Sex'] == self.sex]
253
- row = df2.iloc[(df2['Agemos'] - self.age).abs().argsort()[:1]]
345
+ Args:
346
+ return_json (bool): If True, returns a JSON string. Default False.
254
347
 
255
- L = float(row['L'].values[0])
256
- M = float(row['M'].values[0])
257
- S = float(row['S'].values[0])
348
+ Returns:
349
+ dict or JSON: Z-score, percentile, and interpretation.
350
+ """
351
+ df = self.df_lenage
352
+ sex = self._normalize_sex()
353
+ df2 = df[df['Sex'] == sex]
258
354
 
259
- if L != 0:
260
- z = ((self.bmi / M) ** L - 1) / (L * S)
261
- else:
262
- z = math.log(self.bmi / M) / S
355
+ row = df2.iloc[(df2['Agemos'] - self.age).abs().argsort()[:1]]
356
+ L, M, S = float(row['L'].values[0]), float(row['M'].values[0]), float(row['S'].values[0])
357
+
358
+ if L != 0:
359
+ z = ((self.length / M) ** L - 1) / (L * S)
360
+ else:
361
+ z = math.log(self.length / M) / S
362
+
363
+ percentile = norm.cdf(z) * 100
364
+
365
+ result = {
366
+ "indicator": "Length-for-Age",
367
+ "value": round(self.length, 2),
368
+ "unit": None,
369
+ "Age_Months": round(self.age, 2),
370
+ "sex": "Male" if sex == "1" else "Female",
371
+
372
+ "z_score": round(z, 2),
373
+ "percentile": round(percentile, 1),
374
+
375
+ "z_interpretation": self._interpret_z(z),
376
+ "percentile_interpretation": self._interpret_percentile(percentile)
377
+ }
378
+
379
+ if return_json:
380
+ import json
381
+ return json.dumps(result)
382
+
383
+ return result
384
+
385
+
386
+ def analyze_bmiage(self, return_json=False):
387
+ """
388
+ Analyze BMI-for-age using LMS method.
389
+
390
+ Args:
391
+ return_json (bool): If True, returns a JSON string. Default False.
392
+
393
+ Returns:
394
+ dict or JSON: Z-score, percentile, and interpretation.
395
+ """
396
+ df = self.df_bmiage
397
+ sex = self._normalize_sex()
398
+ df2 = df[df['Sex'] == sex]
399
+
400
+ row = df2.iloc[(df2['Agemos'] - self.age).abs().argsort()[:1]]
401
+ L, M, S = float(row['L'].values[0]), float(row['M'].values[0]), float(row['S'].values[0])
402
+
403
+ if L != 0:
404
+ z = ((self.bmi / M) ** L - 1) / (L * S)
405
+ else:
406
+ z = math.log(self.bmi / M) / S
407
+
408
+ percentile = norm.cdf(z) * 100
409
+
410
+ result = {
411
+ "indicator": "BMI-for-Age",
412
+ "value": round(self.bmi, 2),
413
+ "unit": None,
414
+ "Age_Months": round(self.age, 2),
415
+ "sex": "Male" if sex == "1" else "Female",
416
+
417
+ "z_score": round(z, 2),
418
+ "percentile": round(percentile, 1),
419
+
420
+ "z_interpretation": self._interpret_z(z),
421
+ "percentile_interpretation": self._interpret_percentile(percentile)
422
+ }
423
+
424
+ if return_json:
425
+ import json
426
+ return json.dumps(result)
263
427
 
264
- percentile = norm.cdf(z) * 100
428
+ return result
265
429
 
266
- return {
267
- "Z": round(z, 2),
268
- "Percentile": round(percentile, 2)
269
- }