σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft Academyالمسارات
Ctrl K
AO
+1.2k متعلّم
تسجيل الدخولانضم ↗
المسارات
AO
+1.2k متعلّم
تسجيل الدخول
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
مسار Python›الوحدة 10 · هندسة Python عالية الأداء›الموضوع 10.12·~50 دمتقدم

Parquet وArrow

الموضع في المسارالوحدة 10 من 11 · الموضوع 12 من 13
10.12 Parquet وArrow
—درجة المختبر— / 100إجمالي XP—التتابع—

صفوف أم أعمدة؟

يحتفظ ملف CSV أو قاعدة مخزّنة بالصفوف بكل سجل معًا: كل حقول الصفقة 1 ثم الصفقة 2 وهكذا. والتحليل نادرًا ما يريد ذلك. فحساب متوسط سعر عشرة ملايين صفقة لا يحتاج إلا عمود price لكن التخطيط بالصفوف يجبر القارئ على تخطي كل الحقول الأخرى. أما التخطيط العمودي فيبقي كل عمود متصلًا فيقرأ الاستعلام الأعمدة التي يسميها فقط وتجاور القيم المتشابهة فتنضغط جيدًا جدًا وتجري العمليات الحسابية على العمود عبر كتلة ذاكرة متراصة واحدة. ولهذا تستعمل بحيرات بيانات الكوانت Parquet على القرص وArrow في الذاكرة.

# The same table as rows and as columns
rows = [{"symbol": "AAPL", "price": 184.25, "qty": 100},
        {"symbol": "MSFT", "price": 410.5, "qty": 50},
        {"symbol": "AAPL", "price": 184.5, "qty": 25}]

columns = {name: [row[name] for row in rows] for name in rows[0]}
print(columns)                                       # {'symbol': ['AAPL', 'MSFT', 'AAPL'], 'price': [184.25, 410.5, 184.5], 'qty': [100, 50, 25]}
print(sum(columns["price"]) / len(rows))             # 259.75   the average only touched one column

back = [{name: columns[name][i] for name in columns} for i in range(len(rows))]
print(back == rows)                                  # True  nothing is lost either way

Parquet: مجموعات الصفوف والإحصاءات والترميزات

يقسم ملف Parquet الجدول إلى مجموعات صفوف (مليون صف مثلًا لكل منها)؛ وداخل المجموعة يُخزَّن كل عمود قطعةً مستقلة ويسجل الملف الحد الأدنى والأقصى لكل قطعة. فاستعلام عن price بين 180 و190 يقرأ الإحصاءات أولًا ويتخطى كل مجموعة لا يمكن أن يحوي مداها سعرًا كهذا (predicate pushdown) ولا يقرأ إلا الأعمدة المطلوبة (projection pushdown). وتُرمَّز الأعمدة أيضًا: عمود قليل القيم المتمايزة كالرمز يُخزَّن قاموسًا صغيرًا مع رمز صحيح صغير لكل صف، ثم تُضغط النتيجة. وغالبًا ما يكون ملف بيانات سوق متكررة أصغر بعشر مرات من CSV.

# Dictionary encoding turns a few distinct values into a small dictionary plus integer codes
def dictionary_encode(values):
    positions, dictionary, codes = {}, [], []
    for value in values:
        if value not in positions:
            positions[value] = len(dictionary)
            dictionary.append(value)
        codes.append(positions[value])
    return dictionary, codes

symbols = ["AAPL", "MSFT", "AAPL", "AAPL", "MSFT", "NVDA"] * 1000
dictionary, codes = dictionary_encode(symbols)
print(dictionary, codes[:6])                         # ['AAPL', 'MSFT', 'NVDA'] [0, 1, 0, 0, 1, 2]
print([dictionary[c] for c in codes] == symbols)     # True  decoding restores the column
print(len(dictionary), "distinct values for", len(symbols), "rows")      # 3 distinct values for 6000 rows
# Statistics let a reader skip whole row groups without opening them
def groups_to_read(stats, low, high):
    return [i for i, (minimum, maximum) in enumerate(stats) if maximum >= low and minimum <= high]

stats = [(180.0, 183.5), (183.0, 186.0), (185.5, 190.0), (189.0, 195.0)]        # (min, max) of price per row group
print(groups_to_read(stats, 184.0, 185.0))           # [1]        only the second group can hold a price in this range
print(groups_to_read(stats, 189.5, 192.0))           # [2, 3]
print(groups_to_read(stats, 200.0, 210.0))           # []         no group needs to be read at all

Arrow وpandas وقراءة أقل

يعرّف Apache Arrow التخطيط العمودي نفسه للذاكرة فتستطيع البرامج واللغات تسليم الجدول لبعضها دون تحويل: pandas وPolars وDuckDB وSpark تستطيع كلها رؤية جدول Arrow واحد دون نسخ. وعمليًا تستعمل pandas.read_parquet(path, columns=["symbol", "price"], filters=[("price", ">", 180)]) التي تدفع اختيار الأعمدة والمرشح إلى القارئ، أو pyarrow.parquet.read_table. واكتب البيانات مقسمة حسب عمود كالتاريخ (df.to_parquet(path, partition_cols=["date"])) ليفتح استعلام ليوم واحد مجلدًا واحدًا. وأبقِ مجموعات الصفوف كبيرة بما يكفي للضغط الجيد وصغيرة بما يكفي ليتجاوز الإحصاء كثيرًا منها، ورتّب البيانات بحسب عمود التصفية ليغطي كل مجموعة مدى ضيقًا.

# Sorting by the filter column makes the row groups cover narrow, non-overlapping ranges
import random

rng = random.Random(7)
prices = [round(rng.uniform(100, 200), 2) for _ in range(10_000)]

def group_stats(values, size):
    groups = [values[i : i + size] for i in range(0, len(values), size)]
    return [(min(g), max(g)) for g in groups]

def groups_to_read(stats, low, high):
    return [i for i, (a, b) in enumerate(stats) if b >= low and a <= high]

unsorted_groups = len(groups_to_read(group_stats(prices, 1000), 150.0, 151.0))
sorted_groups = len(groups_to_read(group_stats(sorted(prices), 1000), 150.0, 151.0))
print(unsorted_groups, sorted_groups)                # 10 1   without sorting every group's range overlaps the query
الملف العمودي للتحليل لا للتعديلتُكتب ملفات Parquet مرة وتُقرأ مرات كثيرة. وتغيير صف واحد يعني إعادة كتابة ملف، فهي تناسب التاريخ (التيكات والشرائط ونتائج الاختبار الرجعي) لا جدول أوامر حيًّا مكانه قاعدة بيانات. أضف البيانات الجديدة ملفات أو أقسامًا جديدة بدل تعديل القديمة. وأبقِ المخطط ثابتًا والطوابع بتوقيت UTC وتذكّر أن أعمدة الأعداد العشرية تحتفظ بالتقريب الذي عرفته سابقًا.
  • يُبقي التخزين العمودي كل عمود معًا: تقرأ الاستعلامات الأعمدة اللازمة فقط وتنضغط القيم المتشابهة جيدًا وتسرع العمليات الحسابية على العمود.
  • يقسم Parquet البيانات إلى مجموعات صفوف بإحصاءات الحد الأدنى والأقصى ويرمّز القيم المكررة بقاموس؛ ويتخطى القراء المجموعات والأعمدة غير اللازمة.
  • Arrow هو التوأم في الذاكرة الذي تتشاركه pandas وPolars وDuckDB؛ قسّم بحسب التاريخ ورتّب بعمود التصفية وأضف بدل التعديل.

التالي: قواعد بيانات السلاسل الزمنية، مخازن مصممة للبيانات ذات الطوابع الزمنية كالتيكات والمقاييس.

المختبر: ما الذي يُقيَّم

  • حوّل الصفوف إلى أعمدة باستخدام to_columns25 نقطة
  • أعد بناء الصفوف باستخدام to_rows25 نقطة
  • قلّص التكرار باستخدام dictionary_encode25 نقطة
  • تجاوز ما يمكن تجاوزه باستخدام groups_to_read25 نقطة
المستكشف · lab-128-1
▾ lab-128-1
…
bash · داخل المتصفح
learner@quantcraft:~/lab-128-1$
لم يتم تحميل Python بعدPythonLn 1, Col 1Spaces: 4UTF-8

هل تريد إعادة ضبط هذا الموضوع؟

سيعود كل ما في lab-128-1 إلى الملفات الابتدائية وستُمسح الطرفية. ستفقد حلّك الحالي ولا يمكن التراجع عن ذلك.

سيُعاد ضبطه
  • main.py ← الشيفرة الابتدائية
  • سجل الطرفية ومخرجات الاختبارات
  • التغييرات غير المُرسلة
سيُحتفظ به
  • أفضل درجة في المختبر
  • نقاط XP والتتابع
  • تقدّم المحاضرة والملاحظات

سجّل الدخول لتشغيل الكود الخاص بك.

قراءة كل المحاضرات مجانية. أنشئ حسابًا أو سجّل الدخول لتشغيل الكود وتنفيذ الاختبارات وتسليم هذا المختبر.

  • بيئة تطوير داخل المتصفح مع طرفية bash — لا حاجة لتثبيت أي شيء
  • تصحيح فوري ونقاط خبرة ومكانك في المسار
إنشاء حسابتسجيل الدخول
أو
المتابعة عبر GitHub