howso-engine 62.2.0__tar.gz → 62.3.0__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.
Files changed (115) hide show
  1. {howso_engine-62.2.0/howso_engine.egg-info → howso_engine-62.3.0}/PKG-INFO +1 -1
  2. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/client/base.py +1 -1
  3. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/client/typing.py +3 -0
  4. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/engine/trainee.py +1 -1
  5. howso_engine-62.3.0/howso/howso-engine/howso.caml +0 -0
  6. howso_engine-62.3.0/howso/howso-engine/migrations/migrations.caml +0 -0
  7. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/howso-engine/version.json +3 -3
  8. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/utilities/__init__.py +2 -0
  9. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/utilities/feature_attributes/abstract_data.py +8 -6
  10. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/utilities/feature_attributes/base.py +10 -2
  11. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/utilities/feature_attributes/pandas.py +4 -7
  12. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/utilities/feature_attributes/protocols.py +4 -0
  13. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/utilities/features.py +7 -3
  14. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/utilities/utilities.py +50 -11
  15. {howso_engine-62.2.0 → howso_engine-62.3.0/howso_engine.egg-info}/PKG-INFO +1 -1
  16. howso_engine-62.3.0/version.json +5 -0
  17. howso_engine-62.2.0/howso/howso-engine/howso.caml +0 -0
  18. howso_engine-62.2.0/howso/howso-engine/migrations/migrations.caml +0 -0
  19. howso_engine-62.2.0/version.json +0 -5
  20. {howso_engine-62.2.0 → howso_engine-62.3.0}/.coveragerc +0 -0
  21. {howso_engine-62.2.0 → howso_engine-62.3.0}/.flake8 +0 -0
  22. {howso_engine-62.2.0 → howso_engine-62.3.0}/.github/CODEOWNERS +0 -0
  23. {howso_engine-62.2.0 → howso_engine-62.3.0}/.github/templates/version_summary.md +0 -0
  24. {howso_engine-62.2.0 → howso_engine-62.3.0}/.github/workflows/build-pr.yml +0 -0
  25. {howso_engine-62.2.0 → howso_engine-62.3.0}/.github/workflows/build-release.yml +0 -0
  26. {howso_engine-62.2.0 → howso_engine-62.3.0}/.github/workflows/build.yml +0 -0
  27. {howso_engine-62.2.0 → howso_engine-62.3.0}/.github/workflows/rebuild-requirements.yml +0 -0
  28. {howso_engine-62.2.0 → howso_engine-62.3.0}/.github/workflows/version-update.yml +0 -0
  29. {howso_engine-62.2.0 → howso_engine-62.3.0}/.gitignore +0 -0
  30. {howso_engine-62.2.0 → howso_engine-62.3.0}/.gitmodules +0 -0
  31. {howso_engine-62.2.0 → howso_engine-62.3.0}/.pylintrc +0 -0
  32. {howso_engine-62.2.0 → howso_engine-62.3.0}/CONTRIBUTING.md +0 -0
  33. {howso_engine-62.2.0 → howso_engine-62.3.0}/LICENSE-3RD-PARTY.txt +0 -0
  34. {howso_engine-62.2.0 → howso_engine-62.3.0}/LICENSE.txt +0 -0
  35. {howso_engine-62.2.0 → howso_engine-62.3.0}/MANIFEST.in +0 -0
  36. {howso_engine-62.2.0 → howso_engine-62.3.0}/Makefile +0 -0
  37. {howso_engine-62.2.0 → howso_engine-62.3.0}/README.md +0 -0
  38. {howso_engine-62.2.0 → howso_engine-62.3.0}/bin/build.sh +0 -0
  39. {howso_engine-62.2.0 → howso_engine-62.3.0}/config/latest-mt-debug-howso.yml +0 -0
  40. {howso_engine-62.2.0 → howso_engine-62.3.0}/config/latest-mt-howso.yml +0 -0
  41. {howso_engine-62.2.0 → howso_engine-62.3.0}/config/latest-mt-noavx-traces-howso.yml +0 -0
  42. {howso_engine-62.2.0 → howso_engine-62.3.0}/config/latest-mt-traces-howso.yml +0 -0
  43. {howso_engine-62.2.0 → howso_engine-62.3.0}/config/latest-st-debug-howso.yml +0 -0
  44. {howso_engine-62.2.0 → howso_engine-62.3.0}/config/latest-st-howso.yml +0 -0
  45. {howso_engine-62.2.0 → howso_engine-62.3.0}/config/latest-st-traces-howso.yml +0 -0
  46. {howso_engine-62.2.0 → howso_engine-62.3.0}/config/powershell/Download-Tzdata.ps1 +0 -0
  47. {howso_engine-62.2.0 → howso_engine-62.3.0}/config/powershell/Helper-Functions.ps1 +0 -0
  48. {howso_engine-62.2.0 → howso_engine-62.3.0}/hatch.toml +0 -0
  49. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/__main__.py +0 -0
  50. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/client/__init__.py +0 -0
  51. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/client/api.py +0 -0
  52. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/client/cache.py +0 -0
  53. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/client/client.py +0 -0
  54. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/client/configuration.py +0 -0
  55. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/client/exceptions.py +0 -0
  56. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/client/feature_flags.py +0 -0
  57. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/client/pandas/__init__.py +0 -0
  58. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/client/pandas/client.py +0 -0
  59. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/client/protocols.py +0 -0
  60. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/client/schemas/__init__.py +0 -0
  61. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/client/schemas/aggregate_reaction.py +0 -0
  62. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/client/schemas/base.py +0 -0
  63. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/client/schemas/group_reaction.py +0 -0
  64. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/client/schemas/project.py +0 -0
  65. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/client/schemas/reaction.py +0 -0
  66. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/client/schemas/session.py +0 -0
  67. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/client/schemas/trainee.py +0 -0
  68. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/client/schemas/version.py +0 -0
  69. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/direct/__init__.py +0 -0
  70. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/direct/_utilities.py +0 -0
  71. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/direct/client.py +0 -0
  72. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/direct/schemas/__init__.py +0 -0
  73. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/direct/schemas/trainee.py +0 -0
  74. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/engine/__init__.py +0 -0
  75. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/engine/client.py +0 -0
  76. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/engine/project.py +0 -0
  77. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/engine/session.py +0 -0
  78. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/scikit/__init__.py +0 -0
  79. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/scikit/scikit.py +0 -0
  80. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/utilities/decorators.py +0 -0
  81. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/utilities/fanout_features.py +0 -0
  82. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/utilities/feature_attributes/__init__.py +0 -0
  83. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/utilities/feature_attributes/infer_feature_attributes.py +0 -0
  84. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/utilities/feature_attributes/relational.py +0 -0
  85. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/utilities/feature_attributes/serializers.py +0 -0
  86. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/utilities/feature_attributes/suggestions.py +0 -0
  87. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/utilities/feature_attributes/time_series.py +0 -0
  88. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/utilities/feature_attributes/warnings.py +0 -0
  89. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/utilities/installation_verification.py +0 -0
  90. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/utilities/internals.py +0 -0
  91. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/utilities/json_wrapper.py +0 -0
  92. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/utilities/locale.py +0 -0
  93. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/utilities/monitors.py +0 -0
  94. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/utilities/posix.py +0 -0
  95. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/utilities/random.py +0 -0
  96. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/utilities/reaction.py +0 -0
  97. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/utilities/testing.py +0 -0
  98. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso/utilities/tokenizing.py +0 -0
  99. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso_engine.egg-info/SOURCES.txt +0 -0
  100. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso_engine.egg-info/dependency_links.txt +0 -0
  101. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso_engine.egg-info/entry_points.txt +0 -0
  102. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso_engine.egg-info/requires.txt +0 -0
  103. {howso_engine-62.2.0 → howso_engine-62.3.0}/howso_engine.egg-info/top_level.txt +0 -0
  104. {howso_engine-62.2.0 → howso_engine-62.3.0}/pyproject.toml +0 -0
  105. {howso_engine-62.2.0 → howso_engine-62.3.0}/pyrightconfig.json +0 -0
  106. {howso_engine-62.2.0 → howso_engine-62.3.0}/requirements-3.11-dev.txt +0 -0
  107. {howso_engine-62.2.0 → howso_engine-62.3.0}/requirements-3.11.txt +0 -0
  108. {howso_engine-62.2.0 → howso_engine-62.3.0}/requirements-3.12-dev.txt +0 -0
  109. {howso_engine-62.2.0 → howso_engine-62.3.0}/requirements-3.12.txt +0 -0
  110. {howso_engine-62.2.0 → howso_engine-62.3.0}/requirements-3.13-dev.txt +0 -0
  111. {howso_engine-62.2.0 → howso_engine-62.3.0}/requirements-3.13.txt +0 -0
  112. {howso_engine-62.2.0 → howso_engine-62.3.0}/requirements-3.14-dev.txt +0 -0
  113. {howso_engine-62.2.0 → howso_engine-62.3.0}/requirements-3.14.txt +0 -0
  114. {howso_engine-62.2.0 → howso_engine-62.3.0}/ruff.toml +0 -0
  115. {howso_engine-62.2.0 → howso_engine-62.3.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: howso-engine
3
- Version: 62.2.0
3
+ Version: 62.3.0
4
4
  Summary: The Howso Engine™ is a natively and fully explainable ML engine, serving as an alternative to black box AI neural networks.
5
5
  Author: Howso Incorporated
6
6
  Author-email: support@howso.com
@@ -4039,7 +4039,7 @@ class AbstractHowsoClient(ABC):
4039
4039
  num_robust_prediction_contributions_samples_per_case : int, optional
4040
4040
  Specifies the number of robust samples to use for each case for
4041
4041
  robust prediction contribution computations. Defaults to 300 +
4042
- 2 * (number of features) when unspecified.
4042
+ 30 * (number of features) with a max of 3000 when unspecified.
4043
4043
  num_robust_residual_samples : int, optional
4044
4044
  Total sample size of model to use (using sampling with replacement)
4045
4045
  for robust mda and residual computation.
@@ -128,6 +128,9 @@ class FeatureBounds(TypedDict, total=False):
128
128
  min: float | str
129
129
  """The minimum value to be output. May be a number or date string."""
130
130
 
131
+ nulls_observed: bool
132
+ """Whether any nulls were observed in this feature."""
133
+
131
134
  observed_max: float | str
132
135
  """The observed maximum value in the data. May be a number, string, or date string."""
133
136
 
@@ -3990,7 +3990,7 @@ class Trainee(BaseTrainee):
3990
3990
  num_robust_prediction_contributions_samples_per_case : int, optional
3991
3991
  Specifies the number of robust samples to use for each case for
3992
3992
  robust prediction contribution computations. Defaults to 300 +
3993
- 2 * (number of features) when unspecified.
3993
+ 30 * (number of features) with a max of 3000 when unspecified.
3994
3994
  num_robust_residual_samples : int, optional
3995
3995
  Total sample size of model to use (using sampling with replacement)
3996
3996
  for robust mda and residual computation.
@@ -1,10 +1,10 @@
1
1
  {
2
- "version": "116.2.0",
2
+ "version": "116.2.3",
3
3
  "dependencies": {
4
4
  "amalgam": "81.1.2"
5
5
  },
6
6
  "display_title": "",
7
7
  "build_date": "",
8
- "sha": "47f268d04853bf5a131b6cfd4f25891001aecad4",
9
- "url": "https://github.com/howsoai/howso-engine/releases/tag/116.2.0"
8
+ "sha": "bce11a73c0c3d7a9c2e4f3d7ec445eff38bab9d2",
9
+ "url": "https://github.com/howsoai/howso-engine/releases/tag/116.2.3"
10
10
  }
@@ -38,6 +38,7 @@ from howso.utilities.utilities import ( # noqa: F401
38
38
  get_hash,
39
39
  get_kwargs,
40
40
  get_optimized_max_chunk_size,
41
+ is_null_value,
41
42
  is_valid_datetime_format,
42
43
  is_valid_uuid,
43
44
  ISO_8601_DATE_FORMAT,
@@ -98,6 +99,7 @@ __all__ = [
98
99
  "get_optimized_max_chunk_size",
99
100
  "infer_fanout_feature_config",
100
101
  "infer_feature_attributes",
102
+ "is_null_value",
101
103
  "is_valid_datetime_format",
102
104
  "is_valid_uuid",
103
105
  "lazy_map",
@@ -401,13 +401,11 @@ class InferFeatureAttributesAbstractData(InferFeatureAttributesBase):
401
401
  ) -> dict | None:
402
402
  # prevent circular import
403
403
  output = dict()
404
- allow_null = True
405
404
  original_type = feature_attributes[feature_name]['original_type']
406
405
  decimal_places = feature_attributes[feature_name].get('decimal_places')
407
406
 
408
- # Only integers by default do not allow nulls.
409
- if original_type.get('data_type') == FeatureType.INTEGER.value:
410
- allow_null = False
407
+ # Rely on schema information about nullability
408
+ allow_null = self.data.is_nullable_column(feature_name)
411
409
 
412
410
  if feature_attributes[feature_name].get('type') == 'continuous':
413
411
  # Grab the natural feature_type and raw_feature_type
@@ -451,14 +449,14 @@ class InferFeatureAttributesAbstractData(InferFeatureAttributesBase):
451
449
  return {
452
450
  'min': 0, 'max': feature_attributes[feature_name]['cycle_length'],
453
451
  'observed_min': time_to_seconds(min_time), 'observed_max': time_to_seconds(max_time),
454
- 'allow_null': True
452
+ 'allow_null': allow_null
455
453
  }
456
454
  else:
457
455
  # Tight bounds
458
456
  return {
459
457
  'min': time_to_seconds(min_time), 'max': time_to_seconds(max_time),
460
458
  'observed_min': time_to_seconds(min_time), 'observed_max': time_to_seconds(max_time),
461
- 'allow_null': True
459
+ 'allow_null': allow_null
462
460
  }
463
461
 
464
462
  if 'date_time_format' in feature_attributes[feature_name]:
@@ -798,3 +796,7 @@ class InferFeatureAttributesAbstractData(InferFeatureAttributesBase):
798
796
  def _get_value_count(self, feature_name: str, value: t.Any) -> int:
799
797
  """Get the number of occurances of the provided value of the provided feature."""
800
798
  return self.data.get_value_count(feature_name, value, max_rows_to_eval=self.max_rows_to_eval)
799
+
800
+ def _contains_nulls(self, feature_name: str) -> bool:
801
+ """Get whether the provided feature contains any nulls."""
802
+ return self.data.contains_nulls(feature_name)
@@ -1097,6 +1097,10 @@ class InferFeatureAttributesBase(ABC):
1097
1097
  # Use `update` on the bounds dictionary in case `allowed` ordinal values have already been set
1098
1098
  bounds.update(self.attributes[feature_name].get("bounds", {}))
1099
1099
  _attributes["bounds"] = bounds
1100
+ # Record whether we have observed any nulls in this column
1101
+ if "bounds" not in _attributes:
1102
+ _attributes["bounds"] = {}
1103
+ _attributes["bounds"]["nulls_observed"] = self._contains_nulls(feature_name)
1100
1104
 
1101
1105
  # Do any features contain data unsupported by the core?
1102
1106
  self._check_unsupported_data(self.attributes)
@@ -1190,6 +1194,10 @@ class InferFeatureAttributesBase(ABC):
1190
1194
 
1191
1195
  return ordered_attributes
1192
1196
 
1197
+ @abstractmethod
1198
+ def _contains_nulls(self, feature_name: str) -> bool:
1199
+ """Get whether the provided feature has any nulls."""
1200
+
1193
1201
  @abstractmethod
1194
1202
  def __call__(self) -> FeatureAttributesBase:
1195
1203
  """Process and return the feature attributes."""
@@ -1962,8 +1970,8 @@ class InferFeatureAttributesBase(ABC):
1962
1970
  )
1963
1971
  return prvc
1964
1972
 
1965
- def _process_rare_values(self, preserve_rare_values_map: PreserveRareValuesMap | t.Literal["all", "off"], # noqa: PLR0912, PLR0915
1966
- preserve_rare_values_config: PreserveRareValuesConfig, max_distilled_cases: int,
1973
+ def _process_rare_values(self, preserve_rare_values_map: PreserveRareValuesMap | t.Literal["all", "off"] | None, # noqa: PLR0912, PLR0915
1974
+ preserve_rare_values_config: PreserveRareValuesConfig | None, max_distilled_cases: int,
1967
1975
  significance_threshold: int, enable_suggestions: bool = True) -> None:
1968
1976
  """Procesess `preserve_rare_values` configuration or make recommendation."""
1969
1977
  _prvc: FullPreserveRareValuesConfig = {}
@@ -452,13 +452,6 @@ class InferFeatureAttributesDataFrame(InferFeatureAttributesBase):
452
452
  decimal_places = feature_attributes[feature_name].get('decimal_places')
453
453
  # only integers by default do not allow nulls
454
454
 
455
- # Non-nullable integer
456
- if column.dtype == np.int64:
457
- allow_null = False
458
- # Nullable integer
459
- elif is_integer_dtype(column.dtype):
460
- allow_null = True
461
-
462
455
  def _as_float(value, dtype):
463
456
  # Convert datatype to float
464
457
  if is_timedelta64_dtype(dtype):
@@ -984,3 +977,7 @@ class InferFeatureAttributesDataFrame(InferFeatureAttributesBase):
984
977
  if value in counts.index:
985
978
  return int(counts.loc[value])
986
979
  return 0
980
+
981
+ def _contains_nulls(self, feature_name: str) -> bool:
982
+ """Get whether the provided feature contains any null values."""
983
+ return bool(self.data[feature_name].isna().any())
@@ -191,6 +191,10 @@ class IFACompatibleADCProtocol(t.Protocol):
191
191
  def get_unique_values(self, column_name: Hashable) -> Collection[t.Any]:
192
192
  """Return the unique values in `column_name`."""
193
193
 
194
+ @abstractmethod
195
+ def is_nullable_column(self, column_name: Hashable) -> bool:
196
+ """Return whether the provided column allows nulls."""
197
+
194
198
 
195
199
  class RelationshipProtocol(t.Protocol):
196
200
  """Protocol for an object representing a relationship in a database."""
@@ -4,7 +4,6 @@ from collections.abc import Iterable, Mapping, Sequence
4
4
  import decimal
5
5
  from enum import Enum
6
6
  from functools import partial
7
- import json
8
7
  import locale
9
8
  from typing import Any, cast
10
9
  import warnings
@@ -675,12 +674,14 @@ class FeatureSerializer:
675
674
  def format_tokenizable_string_column(
676
675
  cls,
677
676
  column: pd.Series,
678
- feature: Mapping[str, Any], # noqa: ARG003
677
+ feature: Mapping[str, Any],
679
678
  tokenizer: TokenizerProtocol | None = None,
680
679
  ) -> pd.Series:
681
680
  """
682
681
  Format tokenizable string column.
683
682
 
683
+ Null values are preserved as None, as there is nothing to detokenize.
684
+
684
685
  Parameters
685
686
  ----------
686
687
  column : pandas.Series
@@ -698,7 +699,10 @@ class FeatureSerializer:
698
699
  The formatted column.
699
700
  """
700
701
  tokenizer = tokenizer or HowsoTokenizer()
701
- return pd.Series(tokenizer.detokenize(json.loads(case)) for case in column)
702
+ return pd.Series(
703
+ None if tokens is None else tokenizer.detokenize(tokens)
704
+ for tokens in destringify_json(column, feature)
705
+ )
702
706
 
703
707
  @classmethod
704
708
  def format_unknown_column(cls, column: pd.Series, feature: Mapping[str, Any]) -> pd.Series: # noqa: ARG003
@@ -771,10 +771,33 @@ def serialize_datetimes( # noqa: C901
771
771
  case[i] = dt_value
772
772
 
773
773
 
774
+ def is_null_value(value: Any) -> bool:
775
+ """
776
+ Determine whether a single case value is null.
777
+
778
+ Unlike :func:`pandas.isna`, this is safe to call on container values such as lists and dicts,
779
+ for which :func:`pandas.isna` returns element-wise results rather than a single bool.
780
+
781
+ Parameters
782
+ ----------
783
+ value : Any
784
+ The case value to check.
785
+
786
+ Returns
787
+ -------
788
+ bool
789
+ True if the value is a null scalar (None, `np.nan`, `pd.NA`, ...).
790
+ """
791
+ return pd.api.types.is_scalar(value) and bool(pd.isna(value))
792
+
793
+
774
794
  def stringify_json(cases: list[list[Any]], features: Iterable[str], feature_attributes: Mapping) -> None:
775
795
  """
776
796
  Ensure that any JSON features have their cases stringified.
777
797
 
798
+ Null values are left as-is so that they are sent to the engine as nulls rather than as the
799
+ JSON string "null".
800
+
778
801
  Parameters
779
802
  ----------
780
803
  cases : list of list of Any
@@ -789,20 +812,25 @@ def stringify_json(cases: list[list[Any]], features: Iterable[str], feature_attr
789
812
  orig_type_info = feature_attributes[feature_name].get("original_type", {})
790
813
  # Applicable if original type is an object (Python list/dict) or string, tokenized into a list
791
814
  if orig_type_info.get("data_type", {}) in ["container", "tokenizable_string"]:
792
- if orig_type_info.get("coercion") == "set":
793
- # If an original type indicates a set, convert to list for serialization
794
- for case_group in cases:
795
- case_group[idx] = json.dumps(list(case_group[idx]))
796
- # Deserialize only
797
- else:
798
- for case_group in cases:
799
- case_group[idx] = json.dumps(case_group[idx])
815
+ coerce_set = orig_type_info.get("coercion") == "set"
816
+ for case_group in cases:
817
+ value = case_group[idx]
818
+ if is_null_value(value):
819
+ case_group[idx] = None
820
+ continue
821
+ if coerce_set:
822
+ # If an original type indicates a set, convert to list for serialization
823
+ value = list(value)
824
+ case_group[idx] = json.dumps(value)
800
825
 
801
826
 
802
- def destringify_json(cases: pd.Series, feature_attributes: Mapping) -> pd.Series: # noqa: ARG001
827
+ def destringify_json(cases: pd.Series, feature_attributes: Mapping) -> pd.Series:
803
828
  """
804
829
  Ensure that any JSON features have their cases destringified.
805
830
 
831
+ Null values are returned as None. A null may arrive either directly as a null value or JSON
832
+ encoded as the literal ``null`` (which `json.loads` decodes to None).
833
+
806
834
  Parameters
807
835
  ----------
808
836
  cases : pd.Series
@@ -813,8 +841,15 @@ def destringify_json(cases: pd.Series, feature_attributes: Mapping) -> pd.Series
813
841
  destringified_cases = []
814
842
  typing_info = feature_attributes.get("original_type", {})
815
843
  for case_to_destringify in cases:
816
- formatted_case = json.loads(case_to_destringify)
817
- if typing_info.get("coercion") == "set":
844
+ if is_null_value(case_to_destringify):
845
+ destringified_cases.append(None)
846
+ continue
847
+ if isinstance(case_to_destringify, (str, bytes, bytearray)):
848
+ # The JSON literal ``null`` decodes to None, which cannot be coerced any further
849
+ formatted_case = json.loads(case_to_destringify)
850
+ else:
851
+ formatted_case = case_to_destringify
852
+ if formatted_case is not None and typing_info.get("coercion") == "set":
818
853
  formatted_case = set(formatted_case)
819
854
  destringified_cases.append(formatted_case)
820
855
  return pd.Series(destringified_cases)
@@ -826,6 +861,8 @@ def tokenize_strings(
826
861
  """
827
862
  Process tokenizable strings in the provided cases by tokenizing or detokenizing them in place.
828
863
 
864
+ Null values are left as-is, as there is nothing to tokenize.
865
+
829
866
  Parameters
830
867
  ----------
831
868
  cases : list of list of Any
@@ -843,6 +880,8 @@ def tokenize_strings(
843
880
  if feature_attributes.get(feature_name, {}).get("original_type", {}).get("data_type") != "tokenizable_string":
844
881
  continue
845
882
  for case_group in cases:
883
+ if is_null_value(case_group[idx]):
884
+ continue
846
885
  case_group[idx] = tokenizer.tokenize(case_group[idx])
847
886
 
848
887
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: howso-engine
3
- Version: 62.2.0
3
+ Version: 62.3.0
4
4
  Summary: The Howso Engine™ is a natively and fully explainable ML engine, serving as an alternative to black box AI neural networks.
5
5
  Author: Howso Incorporated
6
6
  Author-email: support@howso.com
@@ -0,0 +1,5 @@
1
+ {
2
+ "dependencies": {
3
+ "howso-engine": "116.2.3"
4
+ }
5
+ }
@@ -1,5 +0,0 @@
1
- {
2
- "dependencies": {
3
- "howso-engine": "116.2.0"
4
- }
5
- }
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