2025, Sep 23 21:31

Pandas के NumPy NaN को Pydantic में सही तरह वैलिडेट कैसे करें

क्यों Pandas/NumPy के NaN, Pydantic के Literal[np.nan] से नहीं मिलते—और दो समाधान: NumPy scalar को Python float में बदलें या any NaN वैलिडेटर से normalize करें।

Pandas अनुपस्थित संख्यात्मक डेटा को दर्शाने के लिए NaN का बेझिझक उपयोग करता है, लेकिन जब यह NaN किसी Series से आता है, तो वह साधारण Python float नहीं बल्कि एक NumPy स्केलर होता है। यदि आप उन मानों को Literal[np.nan] से टाइप किए गए Pydantic मॉडल में देते हैं, तो वैलिडेशन अप्रत्याशित ढंग से असफल हो सकता है। नीचे देखें कि इसे कैसे पुन: उत्पन्न करें, यह क्यों होता है समझें, और बिना आवश्यकता से अधिक स्कीमा ढीला किए इसे साफ-सुथरे तरीके से कैसे ठीक करें।

वैलिडेशन विफलता का न्यूनतम उदाहरण

आम Pandas पैटर्न से शुरू करें: एक integer Series बनाएँ, reindex करके एक missing मान जोड़ें, और आधारभूत मानों पर इटररेट करें ताकि उन्हें ऐसे Pydantic मॉडल से वैलिडेट किया जा सके जो या तो सकारात्मक पूर्णांक या NaN स्वीकार करता है।

import pandas as pd
import numpy as np
import pydantic
from typing import Union, Literal
# पूर्णांकों की series; अंत में missing मान जोड़ने के लिए reindex करें
ser_missing = pd.Series([1, 2], dtype=np.int64).reindex([0, 1, 2])
# मॉडल: "positive integer" या शाब्दिक NaN
class Gauge(pydantic.BaseModel):
    granularity: Union[pydantic.conint(ge=1), Literal[np.nan]]
# मानों पर इटररेट करें; आखिरी आइटम Pandas का NaN है
for item in ser_missing.values:
    # Pandas से आए NaN पर यह ValidationError उठाता है
    Gauge(granularity=item)

np.nan के साथ मॉडल को सीधे कॉल करना काम करता है, फिर भी Series से आए NaN का वैलिडेशन विफल हो जाता है। इसका मतलब है कि Pandas ने जो ऑब्जेक्ट दिया वह Literal[np.nan] से मेल खाने वाले ऑब्जेक्ट जैसा नहीं है।

वास्तव में गड़बड़ क्या है

Pandas missing मान के लिए एक NumPy स्केलर लौटाता है—विशेष रूप से numpy.float64('nan'). Pydantic में Literal[np.nan] Python के NaN (float('nan')) से मेल खाता है, NumPy वाले NaN से नहीं। मूल्य-स्तर पर दोनों NaN ही हैं, लेकिन Literal मिलान के लिए ये एक ही ऑब्जेक्ट नहीं हैं, इसलिए literal जाँच NumPy स्केलर को अस्वीकार कर देती है।

इसे ठीक करने के दो सटीक तरीके

पहली रणनीति यह है कि Literal[np.nan] चलने से पहले NumPy के फ़्लोटिंग स्केलरों को Python float में रूपांतरित कर दें। इससे numpy.float64('nan') float('nan') में बदल जाता है, जिसे literal स्वीकार कर लेता है।

from typing import Union, Literal
from typing_extensions import Annotated
from pydantic import BaseModel, PositiveInt, BeforeValidator
import numpy as np
# NumPy के floating scalars को Python floats में बदलें
def to_builtin_float(value):
    if isinstance(value, np.floating):
        return float(value)
    return value
OnlyNaN = Annotated[Literal[np.nan], BeforeValidator(to_builtin_float)]
class GaugeFixed(BaseModel):
    granularity: Union[PositiveInt, OnlyNaN]
# Pandas मानों के साथ उपयोग
for val in ser_missing.values:
    GaugeFixed(granularity=val)

दूसरी रणनीति में एक “any NaN” प्रकार परिभाषित करना है, जो वैलिडेटर के जरिए Python float NaN या NumPy NaN—दोनों—को स्वीकार करे। इससे Literal मिलान पूरी तरह से बच जाता है और ध्यान सीधे इस अर्थपरक गुण पर रहता है: “NaN है या नहीं”.

from typing import Union
from typing_extensions import Annotated
from pydantic import BaseModel, PositiveInt, AfterValidator
import numpy as np
import math
# कोई भी NaN (Python या NumPy) स्वीकार करता है और उसे float('nan') में सामान्यीकृत करता है
def require_nan(value):
    if isinstance(value, (float, np.floating)) and math.isnan(float(value)):
        return float('nan')
    raise ValueError('not NaN')
AnyNaN = Annotated[float, AfterValidator(require_nan)]
class GaugeStrict(BaseModel):
    granularity: Union[PositiveInt, AnyNaN]
# Pandas मानों के साथ उपयोग
for val in ser_missing.values:
    GaugeStrict(granularity=val)

दोनों तरीकों का निशाना मूल कारण है: NumPy के स्केलर प्रकार और Literal[np.nan] जिस ऑब्जेक्ट से तुलना करता है, उनके बीच असंगति। यदि आपके सेटअप में NumPy NaN मिलने पर coercion अब भी त्रुटि दे, तो “any NaN” वाला मार्ग बेहतर है, क्योंकि वह अर्थपरक गुण की सीधी जाँच करता है।

यह बारीकी क्यों मायने रखती है

डेटा पाइपलाइनों में मान अक्सर Pandas, NumPy और Pydantic के बीच घूमते रहते हैं। छोटे-से प्रकारांतर, जैसे numpy.float64 बनाम float, Literal या सख्त unions पर निर्भर कड़े स्कीमाओं को चुपचाप तोड़ सकते हैं। यदि किसी मॉडल का उद्देश्य NaN को सीमित पूर्णांकों के साथ एक प्रथम-श्रेणी “missing” टोकन की तरह मानना है, तो आपको ऐसा हल चाहिए जो Pandas से निकले डेटा को स्वीकार करे, बिना मनमाने floats के लिए दरवाज़ा खोले।

मुख्य बातें

Pandas के आउटपुट को वैलिडेट करते समय ध्यान रखें कि अनुपस्थित संख्यात्मक मान NumPy scalars के रूप में आते हैं। Literal[np.nan] Python वाले NaN से मेल खाता है और NumPy वाले समकक्ष को अस्वीकार कर देगा। अपने मॉडल को सटीक बनाए रखने के लिए, या तो literal मिलान से पहले NumPy scalars को Python floats में सामान्यीकृत करें, या “any NaN” वैलिडेटर अपनाएँ, जो NaN होने के अर्थपरक गुण को लागू करते हुए पूर्णांकों को सख्ती से सीमित रखता है। इससे मॉडल पूर्वानुमेय रहते हैं, अति-ढीले प्रकारों से बचाव होता है, और Pandas जिस तरह missing डेटा दर्शाता है, उसके साथ साफ़-सुथरा सामंजस्य बनता है।

यह लेख StackOverflow पर प्रश्न (लेखक: MikeFenton) और Dmitry543 के उत्तर पर आधारित है।