well-log-toolkit 0.1.140__py3-none-any.whl → 0.1.142__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.
@@ -1520,6 +1520,232 @@ class Property(PropertyOperationsMixin):
1520
1520
  precision=precision
1521
1521
  )
1522
1522
 
1523
+ def discrete_summary(self, precision: int = 6) -> dict:
1524
+ """
1525
+ Compute summary statistics for discrete/categorical properties.
1526
+
1527
+ This method is designed for discrete logs (like facies, lithology flags,
1528
+ or net/gross indicators) where categorical statistics are more meaningful
1529
+ than continuous statistics like mean or standard deviation.
1530
+
1531
+ Parameters
1532
+ ----------
1533
+ precision : int, default 6
1534
+ Number of decimal places for rounding numeric results
1535
+
1536
+ Returns
1537
+ -------
1538
+ dict
1539
+ Nested dictionary with statistics for each discrete value.
1540
+ If secondary properties (filters) exist, the structure is hierarchical.
1541
+
1542
+ For each discrete value, includes:
1543
+ - label: Human-readable name (if labels defined)
1544
+ - code: Numeric code for this category
1545
+ - count: Number of samples with this value
1546
+ - thickness: Total depth interval (meters) for this category
1547
+ - fraction: Proportion of total thickness (0-1)
1548
+ - depth_range: {min, max} depth extent
1549
+
1550
+ Examples
1551
+ --------
1552
+ >>> # Simple discrete summary (no filters)
1553
+ >>> facies = well.get_property('Facies')
1554
+ >>> stats = facies.discrete_summary()
1555
+ >>> # {'Sand': {'code': 1, 'count': 150, 'thickness': 25.5, 'fraction': 0.45, ...},
1556
+ >>> # 'Shale': {'code': 2, 'count': 180, 'thickness': 30.8, 'fraction': 0.55, ...}}
1557
+
1558
+ >>> # Grouped by zones
1559
+ >>> filtered = facies.filter('Well_Tops')
1560
+ >>> stats = filtered.discrete_summary()
1561
+ >>> # {'Zone_A': {'Sand': {...}, 'Shale': {...}},
1562
+ >>> # 'Zone_B': {'Sand': {...}, 'Shale': {...}}}
1563
+
1564
+ Notes
1565
+ -----
1566
+ For continuous properties, use `sums_avg()` instead.
1567
+ """
1568
+ # Calculate gross thickness for fraction calculation
1569
+ full_intervals = compute_intervals(self.depth)
1570
+ valid_mask = ~np.isnan(self.values)
1571
+ gross_thickness = float(np.sum(full_intervals[valid_mask]))
1572
+
1573
+ if not self.secondary_properties:
1574
+ # No filters, compute stats for all discrete values
1575
+ return self._compute_discrete_stats(
1576
+ np.ones(len(self.depth), dtype=bool),
1577
+ gross_thickness=gross_thickness,
1578
+ precision=precision
1579
+ )
1580
+
1581
+ # Build hierarchical grouping
1582
+ return self._recursive_discrete_group(
1583
+ 0,
1584
+ np.ones(len(self.depth), dtype=bool),
1585
+ gross_thickness=gross_thickness,
1586
+ precision=precision
1587
+ )
1588
+
1589
+ def _recursive_discrete_group(
1590
+ self,
1591
+ filter_idx: int,
1592
+ mask: np.ndarray,
1593
+ gross_thickness: float,
1594
+ precision: int = 6
1595
+ ) -> dict:
1596
+ """
1597
+ Recursively group discrete statistics by secondary properties.
1598
+
1599
+ Parameters
1600
+ ----------
1601
+ filter_idx : int
1602
+ Index of current secondary property
1603
+ mask : np.ndarray
1604
+ Boolean mask for current group
1605
+ gross_thickness : float
1606
+ Total gross thickness for fraction calculation
1607
+ precision : int, default 6
1608
+ Number of decimal places for rounding
1609
+
1610
+ Returns
1611
+ -------
1612
+ dict
1613
+ Discrete stats dict or nested dict of stats
1614
+ """
1615
+ if filter_idx >= len(self.secondary_properties):
1616
+ # Base case: compute discrete statistics for this group
1617
+ return self._compute_discrete_stats(mask, gross_thickness, precision)
1618
+
1619
+ # Get unique values for current filter
1620
+ current_filter = self.secondary_properties[filter_idx]
1621
+ current_filter_values = current_filter.values
1622
+ filter_values = current_filter_values[mask]
1623
+ unique_vals = np.unique(filter_values[~np.isnan(filter_values)])
1624
+
1625
+ if len(unique_vals) == 0:
1626
+ # No valid values, return stats for current mask
1627
+ return self._compute_discrete_stats(mask, gross_thickness, precision)
1628
+
1629
+ # Group by each unique value
1630
+ depth_array = self.depth
1631
+ values_array = self.values
1632
+ full_intervals = compute_intervals(depth_array)
1633
+
1634
+ result = {}
1635
+ for val in unique_vals:
1636
+ sub_mask = mask & (current_filter_values == val)
1637
+
1638
+ # Calculate thickness for THIS group specifically (not the parent)
1639
+ group_valid = sub_mask & ~np.isnan(values_array)
1640
+ group_thickness = float(np.sum(full_intervals[group_valid]))
1641
+
1642
+ # Create readable key with label if available
1643
+ if current_filter.type == 'discrete':
1644
+ int_val = int(val)
1645
+ else:
1646
+ int_val = int(val) if val == int(val) else None
1647
+
1648
+ if current_filter.labels is not None:
1649
+ if int_val is not None and int_val in current_filter.labels:
1650
+ key = current_filter.labels[int_val]
1651
+ elif val in current_filter.labels:
1652
+ key = current_filter.labels[val]
1653
+ elif int_val is not None:
1654
+ key = f"{current_filter.name}_{int_val}"
1655
+ else:
1656
+ key = f"{current_filter.name}_{val:.2f}"
1657
+ elif int_val is not None:
1658
+ key = f"{current_filter.name}_{int_val}"
1659
+ else:
1660
+ key = f"{current_filter.name}_{val:.2f}"
1661
+
1662
+ result[key] = self._recursive_discrete_group(
1663
+ filter_idx + 1, sub_mask, group_thickness, precision
1664
+ )
1665
+
1666
+ return result
1667
+
1668
+ def _compute_discrete_stats(
1669
+ self,
1670
+ mask: np.ndarray,
1671
+ gross_thickness: float,
1672
+ precision: int = 6
1673
+ ) -> dict:
1674
+ """
1675
+ Compute categorical statistics for discrete property values.
1676
+
1677
+ Parameters
1678
+ ----------
1679
+ mask : np.ndarray
1680
+ Boolean mask selecting subset of data
1681
+ gross_thickness : float
1682
+ Total gross thickness for fraction calculation
1683
+ precision : int, default 6
1684
+ Number of decimal places for rounding
1685
+
1686
+ Returns
1687
+ -------
1688
+ dict
1689
+ Dictionary with stats for each discrete value:
1690
+ {value_label: {code, count, thickness, fraction, depth_range}}
1691
+ """
1692
+ values_array = self.values
1693
+ depth_array = self.depth
1694
+
1695
+ values = values_array[mask]
1696
+ depths = depth_array[mask]
1697
+
1698
+ # Compute intervals on full array then mask
1699
+ full_intervals = compute_intervals(depth_array)
1700
+ intervals = full_intervals[mask]
1701
+
1702
+ # Find unique discrete values
1703
+ valid_mask_local = ~np.isnan(values)
1704
+ valid_values = values[valid_mask_local]
1705
+ valid_depths = depths[valid_mask_local]
1706
+ valid_intervals = intervals[valid_mask_local]
1707
+
1708
+ if len(valid_values) == 0:
1709
+ return {}
1710
+
1711
+ unique_vals = np.unique(valid_values)
1712
+
1713
+ result = {}
1714
+ for val in unique_vals:
1715
+ val_mask = valid_values == val
1716
+ val_intervals = valid_intervals[val_mask]
1717
+ val_depths = valid_depths[val_mask]
1718
+
1719
+ thickness = float(np.sum(val_intervals))
1720
+ count = int(np.sum(val_mask))
1721
+ fraction = thickness / gross_thickness if gross_thickness > 0 else 0.0
1722
+
1723
+ # Determine the key and label
1724
+ int_val = int(val)
1725
+ if self.labels is not None and int_val in self.labels:
1726
+ key = self.labels[int_val]
1727
+ label = self.labels[int_val]
1728
+ else:
1729
+ key = f"{self.name}_{int_val}"
1730
+ label = None
1731
+
1732
+ stats = {
1733
+ 'code': int_val,
1734
+ 'count': count,
1735
+ 'thickness': round(thickness, precision),
1736
+ 'fraction': round(fraction, precision),
1737
+ 'depth_range': {
1738
+ 'min': round(float(np.min(val_depths)), precision),
1739
+ 'max': round(float(np.max(val_depths)), precision)
1740
+ }
1741
+ }
1742
+ if label is not None:
1743
+ stats['label'] = label
1744
+
1745
+ result[key] = stats
1746
+
1747
+ return result
1748
+
1523
1749
  def _recursive_group(
1524
1750
  self,
1525
1751
  filter_idx: int,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: well-log-toolkit
3
- Version: 0.1.140
3
+ Version: 0.1.142
4
4
  Summary: Fast LAS file processing with lazy loading and filtering for well log analysis
5
5
  Author-email: Kristian dF Kollsgård <kkollsg@gmail.com>
6
6
  License: MIT
@@ -3,13 +3,13 @@ well_log_toolkit/exceptions.py,sha256=X_fzC7d4yaBFO9Vx74dEIB6xmI9Agi6_bTU3MPxn6k
3
3
  well_log_toolkit/las_file.py,sha256=Tj0mRfX1aX2s6uug7BBlY1m_mu3G50EGxHGzD0eEedE,53876
4
4
  well_log_toolkit/manager.py,sha256=VIARJLkYhxqxgTqfVfAAZU6AVsAPkQWPOUE6RNGnIdY,110558
5
5
  well_log_toolkit/operations.py,sha256=z8j8fGBOwoJGUQFy-Vawjq9nm3OD_dUt0oaNh8yuG7o,18515
6
- well_log_toolkit/property.py,sha256=ol_E4r31ButSnQEzAZjI00ofaRePPcJstRLOFFcgtp4,77108
6
+ well_log_toolkit/property.py,sha256=sMA4sfcI_qXg4L9rdL19gmkzPEyPa_3X7LAFdJNZ6Jk,85224
7
7
  well_log_toolkit/regression.py,sha256=JDcRxaODJnFikAdPJyTq8eUV7iY0vCDmvnGufqlojxs,31625
8
8
  well_log_toolkit/statistics.py,sha256=_huPMbv2H3o9ezunjEM94mJknX5wPK8V4nDv2lIZZRw,16814
9
9
  well_log_toolkit/utils.py,sha256=O2KPq4htIoUlL74V2zKftdqqTjRfezU9M-568zPLme0,6866
10
10
  well_log_toolkit/visualization.py,sha256=nnpmFmbj44TbP0fsnLMR1GaKRkqKCEpI6Fd8Cp0oqBc,204716
11
11
  well_log_toolkit/well.py,sha256=Aav5Y-rui8YsJdvk7BFndNPUu1O9mcjwDApAGyqV9kw,104535
12
- well_log_toolkit-0.1.140.dist-info/METADATA,sha256=Mchq-GINJbLb3Bul25DQ_AI6FjRJWZtsQKra27L1TOk,61388
13
- well_log_toolkit-0.1.140.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
14
- well_log_toolkit-0.1.140.dist-info/top_level.txt,sha256=BMOo7OKLcZEnjo0wOLMclwzwTbYKYh31I8RGDOGSBdE,17
15
- well_log_toolkit-0.1.140.dist-info/RECORD,,
12
+ well_log_toolkit-0.1.142.dist-info/METADATA,sha256=6Lvy02zTkolrANhxaWVTUY0l5HHqhbmRjlOyebp9fC4,61388
13
+ well_log_toolkit-0.1.142.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
14
+ well_log_toolkit-0.1.142.dist-info/top_level.txt,sha256=BMOo7OKLcZEnjo0wOLMclwzwTbYKYh31I8RGDOGSBdE,17
15
+ well_log_toolkit-0.1.142.dist-info/RECORD,,