2025, Nov 01 17:32

Polars LazyFrame में बड़े कॉलम का सैंपल: इंडेक्स‑आधारित, स्ट्रीमिंग‑अनुकूल तरीका

Polars LazyFrame में बड़े, टेक्स्ट‑जैसे कॉलम का sample RAM खत्म कर देता है. इंडेक्स‑आधारित gather से स्ट्रीमिंग‑अनुकूल, मेमोरी‑सुरक्षित सैंपल लेने की तरकीब जानें.

Polars LazyFrame से किसी बहुत बड़े कॉलम का सैंपल लेना ऊपर-ऊपर आसान लगता है, लेकिन प्रक्रिया मेमोरी में फेल हो सकती है। अगर आपका डेटासेट बड़ा है और उसमें कोई एक चौड़ा कॉलम (जैसे वेब पेज का कच्चा पाठ) शामिल है, तो LazyFrame पर सीधे sample करने से पूरा कॉलम materialize हो सकता है और क्रैश हो सकता है। नीचे इस स्थिति को समझने का तरीका और एक वैकल्पिक उपाय दिया है, जो मेमोरी-सीमित वर्कफ़्लो में सहायक हो सकता है।

सेटअप और विफल होने वाला तरीका

डेटा को pl.scan_parquet(...) के जरिए Parquet से स्कैन किया जाता है, क्योंकि पूरा DataFrame मेमोरी में समा नहीं पाएगा। एक बड़े कॉलम का सैंपल लेते ही पाइपलाइन फेल हो जाती है—चाहे मांगा गया सैंपल आकार 1 ही क्यों न हो—जबकि यही क्रिया किसी छोटे कॉलम पर सफल रहती है।

import polars as pl
src_path = "path/to/data.parquet"
lz = pl.scan_parquet(src_path)
samp_n = 1  # उदाहरण आकार; कॉलम बहुत बड़ा हो तो यह भी मेमोरी ओवरफ्लो करा सकता है
subset = lz.select(
    pl.col("content_col").sample(n=samp_n, seed=0)
)
# (1) यहाँ इस परिदृश्य में एक ही बार में लिखना विफल रहता है
subset.sink_parquet("subset.parquet")
# (2) सैंपल किए गए परिणाम को collect करना भी विफल होता है
subset.collect()

वास्तव में हो क्या रहा है

आम तौर पर collect डिफ़ॉल्ट रूप से स्ट्रीमिंग इंजन का उपयोग करता है, लेकिन इस संदर्भ में sample स्ट्रीमिंग के लिए पर्याप्त रूप से ऑप्टिमाइज़ नहीं है और इंजन को पूरा कॉलम मेमोरी में पढ़ने के लिए मजबूर कर सकता है। बहुत बड़े, टेक्स्ट-जैसे कॉलम के साथ, इतना ही उपलब्ध RAM से आगे निकल जाने के लिए काफी है—भले ही अंतिम सैंपल खुद छोटा हो।

मेमोरी के प्रति अधिक सावधान रास्ता

sample के बजाय, स्पष्ट row indices के आधार पर चयन करें। पहले कुल पंक्तियों की संख्या निकालें, फिर यादृच्छिक इंडेक्सों का सेट बनाएं, और केवल उन्हीं पंक्तियों को gather करें। मुख्य बात यह है कि ऐसे ऑपरेशन से बचें जो परोक्ष रूप से पूरे कॉलम को materialize कर दे। नीचे यह तरीका दिखाया गया है।

import polars as pl
import numpy as np
plan = pl.LazyFrame({"x": [1, 2, 3], "y": [4, 5, 6]})
k = 2
total_rows = plan.select(pl.len()).collect().item()
take_idx = sorted(np.random.choice(total_rows, size=k, replace=False))
plan.select(pl.col("x").gather(take_idx)).collect()

यह तरीका पहले से गिने गए row indices की सूची के साथ gather का उपयोग करता है। व्यवहार में परखने वाली बात यह है कि आपकी पाइपलाइन के पथ में gather क्या स्ट्रीमिंग का पर्याप्त लाभ लेकर मेमोरी खत्म होने से बचा पाता है। यदि हाँ, तो यह पैटर्न आपको बहुत बड़े डेटासेट से बिना मेमोरी बढ़ाए एक छोटा, सचमुच यादृच्छिक अंश लेने देता है।

यह याद रखना क्यों उपयोगी है

लेज़ी, स्ट्रीमिंग-प्रथम इंजनों में हर अभिव्यक्ति का व्यवहार मायने रखता है। दो दिखने में मिलते-जुलते ऑपरेशन बहुत अलग execution plan बना सकते हैं। यह जानना कि sample सबकुछ मेमोरी में खींच सकता है, जबकि इंडेक्स-चालित चयन इससे बचा सकता है, RAM की पाबंदी में आपके पास एक ठोस रणनीति देता है।

समापन

यदि Polars LazyFrame में बड़े कॉलम का सैंपल लेते समय out-of-memory त्रुटियाँ आती हैं, तो sample से बचें और इंडेक्स-आधारित चयन अपनाएँ: पंक्तियों की गिनती निकालें, यादृच्छिक पंक्ति स्थितियाँ चुनें, और उन्हीं पंक्तियों को gather करें। इससे वर्कफ़्लो स्ट्रीमिंग निष्पादन मॉडल के करीब रहता है और पूरे, भारी कॉलम के materialize होने की संभावना घटती है। अपने परिवेश में, खासकर उसी चौड़े कॉलम पर जिसने क्रैश कराया था, इस तरीके की जाँच करें और अपनी सैंपलिंग लॉजिक को उन ऑपरेशनों के साथ संरेखित रखें जिन्हें इंजन कुशलतापूर्वक प्लान कर सकता है।

यह लेख StackOverflow के एक प्रश्न (लेखक: q.uijote) और BallpointBen के उत्तर पर आधारित है।