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/infant.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import pandas as pd
2
2
  import matplotlib.pyplot as plt
3
+ import json
3
4
  import math
4
5
  from scipy.stats import norm
5
6
 
@@ -66,181 +67,349 @@ class Infant(Patient):
66
67
 
67
68
  self.hc = hc
68
69
 
69
- def plot_wtageinf(self, title=None):
70
+ def plot_wtageinf(self, title=None, figsize=(12,7), show=True, ax=None):
70
71
  """
71
- Plot weight-for-age growth chart.
72
+ Plot weight-for-age growth chart for infants.
72
73
 
73
- Displays percentile curves and overlays patient's weight.
74
+ This method draws CDC infant weight percentile curves
75
+ and overlays the patient's weight measurement.
74
76
 
75
- Args:
76
- title (str, optional):
77
- Title of the plot.
78
- """
77
+ The resulting figure can be customized, embedded
78
+ in applications, or exported as an image file.
79
79
 
80
- df = self.df_wtageinf
80
+ Args:
81
+ title (str, optional):
82
+ Custom title for the chart.
81
83
 
82
- self.sex = str(self.sex).lower()
84
+ figsize (tuple, optional):
85
+ Figure size in inches (width, height).
86
+ Default is (12, 7).
83
87
 
84
- if self.sex in ['m', '1']:
85
- self.sex = '1'
86
- elif self.sex in ['f', '2']:
87
- self.sex = '2'
88
- else:
89
- raise TypeError('define sex as m or f')
88
+ show (bool, optional):
89
+ Whether to display the plot using matplotlib.
90
+ If False, the plot will not be shown.
91
+ Default is True.
92
+
93
+ ax (matplotlib.axes.Axes, optional):
94
+ Existing matplotlib Axes object for embedding
95
+ in custom layouts or applications.
90
96
 
91
- data = df[df['Sex'] == self.sex]
97
+ Returns:
98
+ tuple:
99
+ (fig, ax)
92
100
 
93
- plt.figure(figsize=(12, 7))
101
+ fig : matplotlib.figure.Figure
102
+ The created matplotlib figure.
103
+
104
+ ax : matplotlib.axes.Axes
105
+ The axes containing the plot.
106
+ """
107
+
108
+ df = self.df_wtageinf
109
+ sex = self._normalize_sex()
110
+
111
+ data = df[df['Sex'] == sex]
112
+
113
+ if ax is None:
114
+ fig, ax = plt.subplots(figsize=figsize)
115
+ else:
116
+ fig = ax.figure
94
117
 
95
118
  for i, col in enumerate(self.agecolumns[1:]):
96
119
 
97
120
  if i in self.highlight_indices:
98
- plt.plot(
121
+ ax.plot(
99
122
  data['Agemos'], data[col],
100
123
  color='gray', linewidth=2,
101
- label=f'{title} {col}'
124
+ label=col
102
125
  )
103
126
  else:
104
- plt.plot(
127
+ ax.plot(
105
128
  data['Agemos'], data[col],
106
129
  color='lightgray', linewidth=1
107
130
  )
108
131
 
109
132
  if self.wt is not None:
110
- plt.scatter(self.age, self.wt, color='red',
111
- s=100, zorder=5)
133
+ ax.scatter(
134
+ self.age, self.wt,
135
+ color='red', s=100,
136
+ zorder=5, label="Patient"
137
+ )
112
138
 
113
- plt.title(title)
114
- plt.xlabel("age(month)")
115
- plt.ylabel("Weight(Kg)")
116
- plt.grid(True)
117
- plt.legend()
118
- plt.show()
139
+ ax.set_title(title or "Infant Weight for Age")
140
+ ax.set_xlabel("Age (months)")
141
+ ax.set_ylabel("Weight (Kg)")
142
+ ax.grid(True)
143
+ ax.legend()
119
144
 
120
- def plot_lenageinf(self, title=None):
121
- """
122
- Plot length-for-age growth chart.
145
+ if show:
146
+ plt.show()
123
147
 
124
- Displays percentile curves and overlays patient's length.
148
+ return fig, ax
125
149
 
126
- Args:
127
- title (str, optional):
128
- Title of the plot.
150
+
151
+ def plot_lenageinf(self, title=None, figsize=(12,7), show=True, ax=None):
129
152
  """
153
+ Plot length-for-age growth chart for infants.
130
154
 
131
- df = self.df_lenageinf
155
+ This method draws CDC infant length percentile curves
156
+ and overlays the patient's length measurement.
132
157
 
133
- self.sex = str(self.sex).lower()
158
+ The generated figure can be customized, embedded
159
+ in applications, or saved to disk.
134
160
 
135
- if self.sex in ['m', '1']:
136
- self.sex = '1'
137
- elif self.sex in ['f', '2']:
138
- self.sex = '2'
139
- else:
140
- raise TypeError('define sex as m or f')
161
+ Args:
162
+ title (str, optional):
163
+ Custom title for the chart.
141
164
 
142
- data = df[df['Sex'] == self.sex]
165
+ figsize (tuple, optional):
166
+ Figure size in inches (width, height).
167
+ Default is (12, 7).
143
168
 
144
- plt.figure(figsize=(14, 10))
169
+ show (bool, optional):
170
+ Whether to display the plot using matplotlib.
171
+ If False, the plot will not be shown.
172
+ Default is True.
173
+
174
+ ax (matplotlib.axes.Axes, optional):
175
+ Existing matplotlib Axes object for embedding
176
+ in custom layouts or applications.
177
+
178
+ Returns:
179
+ tuple:
180
+ (fig, ax)
181
+
182
+ fig : matplotlib.figure.Figure
183
+ The created matplotlib figure.
184
+
185
+ ax : matplotlib.axes.Axes
186
+ The axes containing the plot.
187
+ """
188
+
189
+ df = self.df_lenageinf
190
+ sex = self._normalize_sex()
191
+
192
+ data = df[df['Sex'] == sex]
193
+
194
+ if ax is None:
195
+ fig, ax = plt.subplots(figsize=figsize)
196
+ else:
197
+ fig = ax.figure
145
198
 
146
199
  for i, col in enumerate(self.agecolumns[1:]):
147
200
 
148
201
  if i in self.highlight_indices:
149
- plt.plot(
202
+ ax.plot(
150
203
  data['Agemos'], data[col],
151
204
  color='gray', linewidth=2,
152
- label=f'{title} {col}'
205
+ label=col
153
206
  )
154
207
  else:
155
- plt.plot(
208
+ ax.plot(
156
209
  data['Agemos'], data[col],
157
210
  color='lightgray', linewidth=1
158
211
  )
159
212
 
160
213
  if self.length is not None:
161
- plt.scatter(self.age, self.length,
162
- color='red', s=100, zorder=5)
214
+ ax.scatter(
215
+ self.age, self.length,
216
+ color='red', s=100,
217
+ zorder=5, label="Patient"
218
+ )
163
219
 
164
- plt.title(title)
165
- plt.xlabel("age(month)")
166
- plt.ylabel("Length(cm)")
167
- plt.grid(True)
168
- plt.legend()
169
- plt.show()
220
+ ax.set_title(title or "Infant Length for Age")
221
+ ax.set_xlabel("Age (months)")
222
+ ax.set_ylabel("Length (cm)")
223
+ ax.grid(True)
224
+ ax.legend()
170
225
 
171
- def plot_hcageinf(self, title=None):
172
- """
173
- Plot head circumference-for-age growth chart.
226
+ if show:
227
+ plt.show()
174
228
 
175
- Displays percentile curves and overlays patient's head circumference.
229
+ return fig, ax
176
230
 
177
- Args:
178
- title (str, optional):
179
- Title of the plot.
231
+
232
+ def plot_hcageinf(self, title=None, figsize=(12,7), show=True, ax=None):
180
233
  """
234
+ Plot head circumference-for-age growth chart for infants.
181
235
 
182
- df = self.df_hcageinf
236
+ This method draws CDC head circumference percentile curves
237
+ and overlays the patient's head circumference measurement.
183
238
 
184
- self.sex = str(self.sex).lower()
239
+ The output figure can be customized, embedded in apps,
240
+ or exported as an image file.
185
241
 
186
- if self.sex in ['m', '1']:
187
- self.sex = '1'
188
- elif self.sex in ['f', '2']:
189
- self.sex = '2'
190
- else:
191
- raise TypeError('define sex as m or f')
242
+ Args:
243
+ title (str, optional):
244
+ Custom title for the chart.
245
+
246
+ figsize (tuple, optional):
247
+ Figure size in inches (width, height).
248
+ Default is (12, 7).
249
+
250
+ show (bool, optional):
251
+ Whether to display the plot using matplotlib.
252
+ If False, the plot will not be shown.
253
+ Default is True.
192
254
 
193
- data = df[df['Sex'] == self.sex]
255
+ ax (matplotlib.axes.Axes, optional):
256
+ Existing matplotlib Axes object for embedding
257
+ in custom layouts or applications.
194
258
 
195
- plt.figure(figsize=(12, 7))
259
+ Returns:
260
+ tuple:
261
+ (fig, ax)
262
+
263
+ fig : matplotlib.figure.Figure
264
+ The created matplotlib figure.
265
+
266
+ ax : matplotlib.axes.Axes
267
+ The axes containing the plot.
268
+ """
269
+
270
+ df = self.df_hcageinf
271
+ sex = self._normalize_sex()
272
+
273
+ data = df[df['Sex'] == sex]
274
+
275
+ if ax is None:
276
+ fig, ax = plt.subplots(figsize=figsize)
277
+ else:
278
+ fig = ax.figure
196
279
 
197
280
  for i, col in enumerate(self.agecolumns[1:]):
198
281
 
199
282
  if i in self.highlight_indices:
200
- plt.plot(
283
+ ax.plot(
201
284
  data['Agemos'], data[col],
202
285
  color='gray', linewidth=2,
203
- label=f'{title} {col}'
286
+ label=col
204
287
  )
205
288
  else:
206
- plt.plot(
289
+ ax.plot(
207
290
  data['Agemos'], data[col],
208
291
  color='lightgray', linewidth=1
209
292
  )
210
293
 
211
294
  if self.hc is not None:
212
- plt.scatter(self.age, self.hc,
213
- color='red', s=100, zorder=5)
295
+ ax.scatter(
296
+ self.age, self.hc,
297
+ color='red', s=100,
298
+ zorder=5, label="Patient"
299
+ )
300
+
301
+ ax.set_title(title or "Head Circumference for Age")
302
+ ax.set_xlabel("Age (months)")
303
+ ax.set_ylabel("HC (cm)")
304
+ ax.grid(True)
305
+ ax.legend()
306
+
307
+ if show:
308
+ plt.show()
309
+
310
+ return fig, ax
311
+
312
+ def plot_wtleninf(self, title=None, figsize=(12,7), show=True, ax=None):
313
+ """
314
+ Plot weight-for-length growth chart for infants.
315
+
316
+ This method draws CDC weight-for-length percentile curves
317
+ and overlays the patient's weight and length measurement.
318
+
319
+ It is mainly used for nutritional and growth status
320
+ assessment in infants.
321
+
322
+ The generated figure can be customized, embedded in
323
+ applications, or exported as an image file.
324
+
325
+ Args:
326
+ title (str, optional):
327
+ Custom title for the chart.
328
+
329
+ figsize (tuple, optional):
330
+ Figure size in inches (width, height).
331
+ Default is (12, 7).
332
+
333
+ show (bool, optional):
334
+ Whether to display the plot using matplotlib.
335
+ If False, the plot will not be shown.
336
+ Default is True.
337
+
338
+ ax (matplotlib.axes.Axes, optional):
339
+ Existing matplotlib Axes object for embedding
340
+ in custom layouts or applications.
341
+
342
+ Returns:
343
+ tuple:
344
+ (fig, ax)
345
+
346
+ fig : matplotlib.figure.Figure
347
+ The created matplotlib figure.
348
+
349
+ ax : matplotlib.axes.Axes
350
+ The axes containing the plot.
351
+ """
352
+
353
+ df = self.df_wtleninf
354
+ sex = self._normalize_sex()
355
+
356
+ data = df[df['Sex'] == sex]
357
+
358
+ if ax is None:
359
+ fig, ax = plt.subplots(figsize=figsize)
360
+ else:
361
+ fig = ax.figure
362
+
363
+ for i, col in enumerate(self.lencolumns[1:]):
364
+
365
+ if i in self.highlight_indices:
366
+ ax.plot(
367
+ data['Length'], data[col],
368
+ color='gray', linewidth=2,
369
+ label=col
370
+ )
371
+ else:
372
+ ax.plot(
373
+ data['Length'], data[col],
374
+ color='lightgray', linewidth=1
375
+ )
214
376
 
215
- plt.title(title)
216
- plt.xlabel("age(month)")
217
- plt.ylabel("Head Circumference(cm)")
218
- plt.grid(True)
219
- plt.legend()
220
- plt.show()
377
+ if self.wt is not None and self.length is not None:
378
+ ax.scatter(
379
+ self.length, self.wt,
380
+ color='red', s=100,
381
+ zorder=5, label="Patient"
382
+ )
221
383
 
222
- def analyze_wtageinf(self):
384
+ ax.set_title(title or "Weight for Length")
385
+ ax.set_xlabel("Length (cm)")
386
+ ax.set_ylabel("Weight (Kg)")
387
+ ax.grid(True)
388
+ ax.legend()
389
+
390
+ if show:
391
+ plt.show()
392
+
393
+ return fig, ax
394
+
395
+
396
+
397
+ def analyze_wtageinf(self, as_json=False):
223
398
  """
224
- Analyze weight-for-age using LMS method.
399
+ Analyze weight-for-age using LMS method (WHO/CDC).
225
400
 
226
401
  Returns:
227
- dict:
228
- Z-score and percentile.
402
+ dict or json:
403
+ Z-score, percentile, and interpretation.
229
404
  """
230
405
 
231
406
  df = self.df_wtageinf
232
407
 
233
- self.sex = str(self.sex).lower()
408
+ sex = self._normalize_sex()
234
409
 
235
- if self.sex in ['m', '1']:
236
- self.sex = '1'
237
- elif self.sex in ['f', '2']:
238
- self.sex = '2'
239
- else:
240
- raise TypeError('define sex as m or f')
241
-
242
- df2 = df[df['Sex'] == self.sex]
410
+ df2 = df[df['Sex'] == sex]
243
411
 
412
+ # Closest age row
244
413
  row = df2.iloc[
245
414
  (df2['Agemos'] - self.age).abs().argsort()[:1]
246
415
  ]
@@ -249,6 +418,7 @@ class Infant(Patient):
249
418
  M = float(row['M'].values[0])
250
419
  S = float(row['S'].values[0])
251
420
 
421
+ # LMS Z-score
252
422
  if L != 0:
253
423
  z = ((self.wt / M) ** L - 1) / (L * S)
254
424
  else:
@@ -256,133 +426,157 @@ class Infant(Patient):
256
426
 
257
427
  percentile = norm.cdf(z) * 100
258
428
 
259
- return {
260
- "Z": round(z, 2),
261
- "Percentile": round(percentile, 2)
429
+ result = {
430
+ "indicator": "Weight-for-Age",
431
+ "value": round(self.wt, 2),
432
+ "unit": "kg",
433
+ "age_months": round(self.age, 2),
434
+ "sex": "Male" if sex == "1" else "Female",
435
+
436
+ "z_score": round(z, 2),
437
+ "percentile": round(percentile, 1),
438
+
439
+ "z_interpretation": self._interpret_z(z),
440
+ "percentile_interpretation": self._interpret_percentile(percentile)
262
441
  }
263
442
 
264
- def analyze_lenageinf(self):
265
- """
266
- Analyze length-for-age using LMS method.
443
+ if as_json:
444
+ return json.dumps(result, ensure_ascii=False)
267
445
 
268
- Returns:
269
- dict:
270
- Z-score and percentile.
271
- """
446
+ return result
272
447
 
273
- df = self.df_lenageinf
274
448
 
275
- self.sex = str(self.sex).lower()
449
+ def analyze_lenageinf(self, return_json=False):
450
+ """
451
+ Analyze length-for-age using LMS method.
276
452
 
277
- if self.sex in ['m', '1']:
278
- self.sex = '1'
279
- elif self.sex in ['f', '2']:
280
- self.sex = '2'
281
- else:
282
- raise TypeError('define sex as m or f')
453
+ Args:
454
+ return_json (bool): If True, returns a JSON string. Default False.
283
455
 
284
- df2 = df[df['Sex'] == self.sex]
456
+ Returns:
457
+ dict or JSON: Z-score, percentile, and interpretation.
458
+ """
459
+ df = self.df_lenageinf
460
+ sex = self._normalize_sex()
461
+ df2 = df[df['Sex'] == sex]
285
462
 
286
- row = df2.iloc[
287
- (df2['Agemos'] - self.age).abs().argsort()[:1]
288
- ]
463
+ row = df2.iloc[(df2['Agemos'] - self.age).abs().argsort()[:1]]
464
+ L, M, S = float(row['L'].values[0]), float(row['M'].values[0]), float(row['S'].values[0])
289
465
 
290
- L = float(row['L'].values[0])
291
- M = float(row['M'].values[0])
292
- S = float(row['S'].values[0])
466
+ if L != 0:
467
+ z = ((self.length / M) ** L - 1) / (L * S)
468
+ else:
469
+ z = math.log(self.length / M) / S
293
470
 
294
- if L != 0:
295
- z = ((self.length / M) ** L - 1) / (L * S)
296
- else:
297
- z = math.log(self.length / M) / S
471
+ percentile = norm.cdf(z) * 100
298
472
 
299
- percentile = norm.cdf(z) * 100
300
473
 
301
- return {
302
- "Z": round(z, 2),
303
- "Percentile": round(percentile, 2)
304
- }
474
+ result = {
475
+ "indicator": "Length-for-Age",
476
+ "value": round(self.length, 2),
477
+ "unit": "cm",
478
+ "age_months": round(self.age, 2),
479
+ "sex": "Male" if sex == "1" else "Female",
305
480
 
306
- def analyze_hcageinf(self):
307
- """
308
- Analyze head circumference-for-age using LMS method.
481
+ "z_score": round(z, 2),
482
+ "percentile": round(percentile, 1),
309
483
 
310
- Returns:
311
- dict:
312
- Z-score and percentile.
313
- """
484
+ "z_interpretation": self._interpret_z(z),
485
+ "percentile_interpretation": self._interpret_percentile(percentile)
486
+ }
314
487
 
315
- df = self.df_hcageinf
488
+ if return_json:
489
+ import json
490
+ return json.dumps(result)
316
491
 
317
- self.sex = str(self.sex).lower()
492
+ return result
318
493
 
319
- if self.sex in ['m', '1']:
320
- self.sex = '1'
321
- elif self.sex in ['f', '2']:
322
- self.sex = '2'
323
- else:
324
- raise TypeError('define sex as m or f')
325
494
 
326
- df2 = df[df['Sex'] == self.sex]
495
+ def analyze_hcageinf(self, return_json=False):
496
+ """
497
+ Analyze head circumference-for-age using LMS method.
327
498
 
328
- row = df2.iloc[
329
- (df2['Agemos'] - self.age).abs().argsort()[:1]
330
- ]
499
+ Args:
500
+ return_json (bool): If True, returns a JSON string. Default False.
331
501
 
332
- L = float(row['L'].values[0])
333
- M = float(row['M'].values[0])
334
- S = float(row['S'].values[0])
502
+ Returns:
503
+ dict or JSON: Z-score, percentile, and interpretation.
504
+ """
505
+ df = self.df_hcageinf
506
+ sex = self._normalize_sex()
507
+ df2 = df[df['Sex'] == sex]
335
508
 
336
- if L != 0:
337
- z = ((self.hc / M) ** L - 1) / (L * S)
338
- else:
339
- z = math.log(self.hc / M) / S
509
+ row = df2.iloc[(df2['Agemos'] - self.age).abs().argsort()[:1]]
510
+ L, M, S = float(row['L'].values[0]), float(row['M'].values[0]), float(row['S'].values[0])
340
511
 
341
- percentile = norm.cdf(z) * 100
512
+ if L != 0:
513
+ z = ((self.hc / M) ** L - 1) / (L * S)
514
+ else:
515
+ z = math.log(self.hc / M) / S
342
516
 
343
- return {
344
- "Z": round(z, 2),
345
- "Percentile": round(percentile, 2)
346
- }
517
+ percentile = norm.cdf(z) * 100
347
518
 
348
- def analyze_wtleninf(self):
349
- """
350
- Analyze weight-for-length using LMS method.
519
+ result = {
520
+ "indicator": "Head Circumference-for-Age",
521
+ "value": round(self.hc, 2),
522
+ "unit": "cm",
523
+ "age_months": round(self.age, 2),
524
+ "sex": "Male" if sex == "1" else "Female",
351
525
 
352
- Returns:
353
- dict:
354
- Z-score and percentile.
355
- """
526
+ "z_score": round(z, 2),
527
+ "percentile": round(percentile, 1),
356
528
 
357
- df = self.df_wtleninf
529
+ "z_interpretation": self._interpret_z(z),
530
+ "percentile_interpretation": self._interpret_percentile(percentile)
531
+ }
358
532
 
359
- self.sex = str(self.sex).lower()
533
+ if return_json:
534
+ import json
535
+ return json.dumps(result)
360
536
 
361
- if self.sex in ['m', '1']:
362
- self.sex = '1'
363
- elif self.sex in ['f', '2']:
364
- self.sex = '2'
365
- else:
366
- raise TypeError('define sex as m or f')
537
+ return result
367
538
 
368
- df2 = df[df['Sex'] == self.sex]
369
539
 
370
- row = df2.iloc[
371
- (df2['Length'] - self.length).abs().argsort()[:1]
372
- ]
540
+ def analyze_wtleninf(self, return_json=False):
541
+ """
542
+ Analyze weight-for-length using LMS method.
373
543
 
374
- L = float(row['L'].values[0])
375
- M = float(row['M'].values[0])
376
- S = float(row['S'].values[0])
544
+ Args:
545
+ return_json (bool): If True, returns a JSON string. Default False.
377
546
 
378
- if L != 0:
379
- z = ((self.wt / M) ** L - 1) / (L * S)
380
- else:
381
- z = math.log(self.wt / M) / S
547
+ Returns:
548
+ dict or JSON: Z-score, percentile, and interpretation.
549
+ """
550
+ df = self.df_wtleninf
551
+ sex = self._normalize_sex()
552
+ df2 = df[df['Sex'] == sex]
382
553
 
383
- percentile = norm.cdf(z) * 100
554
+ row = df2.iloc[(df2['Length'] - self.length).abs().argsort()[:1]]
555
+ L, M, S = float(row['L'].values[0]), float(row['M'].values[0]), float(row['S'].values[0])
384
556
 
385
- return {
386
- "Z": round(z, 2),
387
- "Percentile": round(percentile, 2)
388
- }
557
+ if L != 0:
558
+ z = ((self.wt / M) ** L - 1) / (L * S)
559
+ else:
560
+ z = math.log(self.wt / M) / S
561
+
562
+ percentile = norm.cdf(z) * 100
563
+
564
+ result = {
565
+ "indicator": "Weight-for-Length",
566
+ "value": round(self.wt, 2),
567
+ "unit": None,
568
+ "length_cm": round(self.length, 2),
569
+ "sex": "Male" if sex == "1" else "Female",
570
+
571
+ "z_score": round(z, 2),
572
+ "percentile": round(percentile, 1),
573
+
574
+ "z_interpretation": self._interpret_z(z),
575
+ "percentile_interpretation": self._interpret_percentile(percentile)
576
+ }
577
+
578
+ if return_json:
579
+ import json
580
+ return json.dumps(result)
581
+
582
+ return result