{"record":{"id":"91795b6fe80ee783","repo":"apache/beam","slug":"zscore-learn-one-expected-univariate-input-but-got-s","errorCode":null,"errorMessage":"ZScore.learn_one expected univariate input, but got %s","messagePattern":"ZScore\\.learn_one expected univariate input, but got (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"sdks/python/apache_beam/ml/anomaly/detectors/zscore.py","lineNumber":92,"sourceCode":"      stdev_tracker: Optional[StdevTracker] = None,\n      **kwargs):\n    if \"threshold_criterion\" not in kwargs:\n      kwargs[\"threshold_criterion\"] = FixedThreshold(3)\n    super().__init__(**kwargs)\n\n    self._sub_stat_tracker = sub_stat_tracker or IncSlidingMeanTracker(\n        DEFAULT_WINDOW_SIZE)\n    self._stdev_tracker = stdev_tracker or IncSlidingStdevTracker(\n        DEFAULT_WINDOW_SIZE)\n\n  def learn_one(self, x: beam.Row) -> None:\n    \"\"\"Updates the mean and standard deviation trackers with a new data point.\n\n    Args:\n      x: A `beam.Row` containing a single numerical value.\n    \"\"\"\n    if len(x.__dict__) != 1:\n      raise ValueError(\n          \"ZScore.learn_one expected univariate input, but got %s\", str(x))\n\n    v = next(iter(x))\n    self._stdev_tracker.push(v)\n    self._sub_stat_tracker.push(v)\n\n  def score_one(self, x: beam.Row) -> Optional[float]:\n    \"\"\"Scores a data point using the Z-Score.\n\n    Args:\n      x: A `beam.Row` containing a single numerical value.\n\n    Returns:\n      float | None: The Z-Score.\n    \"\"\"\n    if len(x.__dict__) != 1:\n      raise ValueError(\n          \"ZScore.score_one expected univariate input, but got %s\", str(x))","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/python/apache_beam/ml/anomaly/detectors/zscore.py#L74-L110","documentation":"ZScore.learn_one updates the mean/standard-deviation trackers and expects x to be a beam.Row with exactly one numerical field. Any other field count raises ValueError. The z-score statistic is inherently univariate, so the detector enforces the input shape at learning time.","triggerScenarios":"Calling ZScore.learn_one with a multi-field or empty beam.Row, e.g. beam.Row(mean=1, std=0.5) or a Row derived from a multi-column record.","commonSituations":"Multi-metric pipelines reused as-is; rows carrying identifiers or timestamps as extra attributes; schema drift in the source PCollection.","solutions":["Project to one numeric field: beam.Map(lambda r: beam.Row(value=r.x)).","Run one detector per feature for multivariate datasets.","Validate row arity upstream with a Map that raises a clearer domain-specific error."],"exampleFix":"// before\nrows | anomaly.ZScore(threshold=3).learn_one()  # Row(a=.., b=..)\n// after\nrows | beam.Map(lambda r: beam.Row(value=r.a)) | anomaly.ZScore(threshold=3).learn_one()","handlingStrategy":"validation","validationCode":"if len(row.__dict__) != 1:\n    raise ValueError(f\"ZScore.learn_one requires exactly one field, got: {list(row.__dict__)}\")","typeGuard":"def is_univariate(row): return len(getattr(row, '__dict__', {})) == 1","tryCatchPattern":"try:\n    detector.learn_one(row)\nexcept ValueError as e:\n    logger.warning(\"skipping row %r: %s\", row, e)","preventionTips":["Project to beam.Row(value=...) before learn_one","Keep IDs/timestamps out of detector input Rows","Share one projection DoFn between learn and score paths"],"tags":["python","anomaly-detection","validation","univariate"],"backgroundTag":"invalid-argument-value","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-20T03:17:13.778Z"}