well-log-toolkit 0.1.140__tar.gz → 0.1.142__tar.gz
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.
- {well_log_toolkit-0.1.140 → well_log_toolkit-0.1.142}/PKG-INFO +1 -1
- {well_log_toolkit-0.1.140 → well_log_toolkit-0.1.142}/pyproject.toml +1 -1
- {well_log_toolkit-0.1.140 → well_log_toolkit-0.1.142}/well_log_toolkit/property.py +226 -0
- {well_log_toolkit-0.1.140 → well_log_toolkit-0.1.142}/well_log_toolkit.egg-info/PKG-INFO +1 -1
- {well_log_toolkit-0.1.140 → well_log_toolkit-0.1.142}/README.md +0 -0
- {well_log_toolkit-0.1.140 → well_log_toolkit-0.1.142}/setup.cfg +0 -0
- {well_log_toolkit-0.1.140 → well_log_toolkit-0.1.142}/well_log_toolkit/__init__.py +0 -0
- {well_log_toolkit-0.1.140 → well_log_toolkit-0.1.142}/well_log_toolkit/exceptions.py +0 -0
- {well_log_toolkit-0.1.140 → well_log_toolkit-0.1.142}/well_log_toolkit/las_file.py +0 -0
- {well_log_toolkit-0.1.140 → well_log_toolkit-0.1.142}/well_log_toolkit/manager.py +0 -0
- {well_log_toolkit-0.1.140 → well_log_toolkit-0.1.142}/well_log_toolkit/operations.py +0 -0
- {well_log_toolkit-0.1.140 → well_log_toolkit-0.1.142}/well_log_toolkit/regression.py +0 -0
- {well_log_toolkit-0.1.140 → well_log_toolkit-0.1.142}/well_log_toolkit/statistics.py +0 -0
- {well_log_toolkit-0.1.140 → well_log_toolkit-0.1.142}/well_log_toolkit/utils.py +0 -0
- {well_log_toolkit-0.1.140 → well_log_toolkit-0.1.142}/well_log_toolkit/visualization.py +0 -0
- {well_log_toolkit-0.1.140 → well_log_toolkit-0.1.142}/well_log_toolkit/well.py +0 -0
- {well_log_toolkit-0.1.140 → well_log_toolkit-0.1.142}/well_log_toolkit.egg-info/SOURCES.txt +0 -0
- {well_log_toolkit-0.1.140 → well_log_toolkit-0.1.142}/well_log_toolkit.egg-info/dependency_links.txt +0 -0
- {well_log_toolkit-0.1.140 → well_log_toolkit-0.1.142}/well_log_toolkit.egg-info/requires.txt +0 -0
- {well_log_toolkit-0.1.140 → well_log_toolkit-0.1.142}/well_log_toolkit.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "well-log-toolkit"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.142"
|
|
8
8
|
description = "Fast LAS file processing with lazy loading and filtering for well log analysis"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -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,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{well_log_toolkit-0.1.140 → well_log_toolkit-0.1.142}/well_log_toolkit.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{well_log_toolkit-0.1.140 → well_log_toolkit-0.1.142}/well_log_toolkit.egg-info/requires.txt
RENAMED
|
File without changes
|
{well_log_toolkit-0.1.140 → well_log_toolkit-0.1.142}/well_log_toolkit.egg-info/top_level.txt
RENAMED
|
File without changes
|