سرنخ — خودآموز برنامه‌نویسی برای هوش مصنوعی (مقدماتی)

فصل ۷ از ۱۰

پیشرفت ترم
۰٪

ترم ۲ · داده: دیدن، تمیز کردن، فهمیدن

نمودار: کدام شکل برای کدام سؤال

فصل ۷پیش‌نمایش رایگان

در این فصل چه یاد می‌گیری#

جدولِ نُه‌عددیِ فصلِ قبل یک روندِ صعودی داشت، ولی برای دیدنش باید عددها را یکی‌یکی می‌خواندی. یک نمودارِ خطی همان را در یک نگاه می‌گوید. در این فصل matplotlib را یاد می‌گیری، ولی موضوعِ اصلی دستورها نیست — این است که برای هر سؤال، کدام شکل جوابِ درست را نشان می‌دهد و کدام‌ها گمراهت می‌کنند. سه شکلِ اصلی را می‌بینی و در هر کدام یک تصمیمِ واقعی می‌گیری.

چهار قابِ خالی که هرکدام نوعِ متفاوتی از شکلِ نمودار را نشان می‌دهند: میله، پراکندگی، خط و توزیع

آخر این فصل می‌توانی:

  • هیستوگرام بکشی و از رویش توزیعِ داده را بخوانی
  • نمودارِ میله‌ای برای مقایسهٔ گروه‌ها بکشی و بگویی چرا باید مرتب باشد
  • نمودارِ خطی برای روندِ زمانی و نمودارِ پراکندگی برای رابطهٔ دو متغیر بکشی
  • بگویی چرا هر نمودارِ بدونِ برچسب بی‌ارزش است
  • بگویی چرا محورِ عمودیِ نامتعارف می‌تواند دروغ بگوید

قبل از شروع#

از فصل ۶: groupby و اینکه یک جدول را به چند عدد خلاصه می‌کند.

از فصل ۵: جدولِ تمیز. سلولِ راه‌اندازی laptops_clean.csv را می‌سازد.

📓 نوت‌بوک: نوت‌بوک این فصل را در Colab باز کن — همهٔ کدهای این فصل آماده و به‌ترتیب داخلش هست.

⚠️ مواظب باش: برچسب‌های این فصل انگلیسی نوشته شده‌اند و این عمدی است. matplotlib در Colab به‌طورِ پیش‌فرض هیچ فونتِ فارسی ندارد، پس متنِ فارسی روی نمودار یا مربعِ خالی می‌شود یا حروفش از هم جدا و برعکس. راهِ رفعش هست (نصبِ فونت + کتابخانهٔ شکل‌دهیِ متن) ولی یک کارِ جانبی است. قاعدهٔ عملی: برچسبِ نمودار را انگلیسی بنویس، توضیحِ نمودار را فارسی.

۱. هیستوگرام: داده چه شکلی است؟#

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

df = pd.read_csv("laptops_clean.csv")

counts, edges = np.histogram(df["price"], bins=10)
print(counts)
print((edges / 1e6).round(1))

plt.figure(figsize=(7, 4))
plt.hist(df["price"] / 1e6, bins=10, edgecolor="white")
plt.xlabel("price (million toman)")
plt.ylabel("number of listings")
plt.title("Price distribution")
plt.show()
[21 40 54 38 46 16  8  4  6  5]
[ 9.  14.1 19.2 24.2 29.3 34.4 39.5 44.6 49.6 54.7 59.8]

هیستوگرام یک ستون را به بازه‌های مساوی می‌شکند و می‌شمارد در هر بازه چند مقدار افتاده. این تنها نموداری است که شکلِ داده را نشان می‌دهد — چیزی که هیچ عددِ خلاصه‌ای نمی‌گوید.

عددها را بخوان: بیشترِ آگهی‌ها در بازهٔ ۱۴ تا ۳۴ میلیون‌اند، و بعد یک دنبالهٔ بلندِ راست هست — چند آگهیِ خیلی گران که تا ۶۰ میلیون کشیده شده‌اند.

همین شکل توضیح می‌دهد چرا در فصلِ ۶ میانگین از میانه بیشتر بود: دنبالهٔ راست، میانگین را به راست می‌کشد.

چک کن: مجموعِ اعدادِ counts باید ۲۳۸ شود — همهٔ ردیف‌ها در یکی از بازه‌ها می‌افتند. اگر کمتر شد، یعنی مقدارِ خالی داری.

🌱 ریشه‌اش کجاست: به این شکل — قله در چپ و دنبالهٔ بلند در راست — می‌گویند توزیعِ چوله به راست. ریشه، ترم ۶ فصل ۷ — توزیع: شکلِ داده شکل‌های اصلیِ توزیع و معنایشان را می‌سازد.

۲. نمودار میله‌ای: مقایسهٔ گروه‌ها#

by_brand = df.groupby("brand")["price"].mean().sort_values()
print(by_brand.round(0))

plt.figure(figsize=(7, 4))
plt.barh(range(len(by_brand)), by_brand.values / 1e6)
plt.yticks(range(len(by_brand)), ["Dell", "HP", "Lenovo", "Asus", "Apple"])
plt.xlabel("average price (million toman)")
plt.title("Average price by brand")
plt.show()
brand
دل       22451111.0
اچ پی    23540000.0
لنوو     23745833.0
ایسوس    24893506.0
اپل      46025000.0
Name: price, dtype: float64

دو تصمیم در این کد هست که هر دو مهم‌اند:

  • sort_values() قبل از رسم. میله‌های نامرتب چشم را وادار می‌کنند مقایسه کند؛ میله‌های مرتب جواب را مستقیم می‌دهند. همیشه نمودارِ میله‌ای را مرتب کن، مگر اینکه ترتیبِ دسته‌ها ذاتاً معنادار باشد (مثلِ ماه‌های سال).
  • barh به‌جای bar. وقتی برچسب‌ها اسم‌اند، میلهٔ افقی خواناتر است چون اسم‌ها روی هم نمی‌افتند.

و نتیجه در یک نگاه دیده می‌شود: چهار برند تقریباً هم‌قیمت‌اند و اپل تنهاست.

۳. نمودار خطی: روند در زمان#

by_year = df.groupby("year")["price"].mean()
print(by_year.round(0).to_dict())

plt.figure(figsize=(7, 4))
plt.plot(by_year.index, by_year.values / 1e6, marker="o")
plt.xlabel("year")
plt.ylabel("average price (million toman)")
plt.title("Average price over time")
plt.grid(alpha=0.3)
plt.show()
{2016: 17226667.0, 2017: 17786364.0, 2018: 21988000.0, 2019: 23173913.0, 2020: 25040000.0, 2021: 30970968.0, 2022: 30440000.0, 2023: 35060714.0, 2024: 36641667.0}

نمودارِ خطی فقط وقتی درست است که محورِ افقی یک ترتیبِ طبیعی داشته باشد — زمان، سن، دما. خط بینِ دو نقطه یعنی «بینشان هم مقداری هست»؛ اگر محورت برند باشد، آن خط یک ادعای بی‌معناست.

آن marker="o" نقطه‌های واقعی را نشان می‌دهد. بدونش نمی‌دانی کجاها داده داری و کجاها فقط خطِ وصل‌کننده است.

📏 اندازه بگیر: به سالِ ۲۰۲۲ نگاه کن — تنها جایی که خط پایین می‌آید. آیا این یک اتفاقِ واقعی است؟ قبل از هر داستانی، count آن سال را نگاه کن. اگر آن سال فقط چند آگهی داشته باشد، آن افت می‌تواند کاملاً تصادفی باشد. قبل از توضیح دادنِ یک تغییر، مطمئن شو که تغییر واقعی است.

۴. نمودار پراکندگی: رابطهٔ دو متغیر#

print(df[["year", "ram_gb", "price"]].corr().round(3))

plt.figure(figsize=(7, 4))
plt.scatter(df["year"], df["price"] / 1e6, alpha=0.4)
plt.xlabel("year")
plt.ylabel("price (million toman)")
plt.title("Price vs year")
plt.grid(alpha=0.3)
plt.show()
         year  ram_gb  price
year    1.000   0.041  0.635
ram_gb  0.041   1.000  0.290
price   0.635   0.290  1.000

نمودارِ پراکندگی هر ردیف را یک نقطه می‌گذارد و رابطهٔ دو ستون را نشان می‌دهد. alpha=0.4 نقطه‌ها را نیمه‌شفاف می‌کند تا جایی که روی هم می‌افتند پُررنگ‌تر شود — بدونِ آن، دویست نقطه یک لکهٔ یکدست می‌شوند.

جدولِ corr() همبستگی هر جفت ستون را می‌دهد: عددی بینِ ۱- و ۱+ که می‌گوید دو متغیر چقدر با هم بالا و پایین می‌روند. year و price همبستگیِ ۰٫۶۳۵ دارند — رابطهٔ نسبتاً قوی. ram_gb و price فقط ۰٫۲۹.

⚠️ مواظب باش: همبستگیِ ۰٫۶۳۵ به‌هیچ‌وجه یعنی «سال باعثِ قیمت می‌شود». دو متغیر می‌توانند با هم حرکت کنند چون یکی دیگری را می‌سازد، چون هر دو از یک عاملِ سوم می‌آیند، یا کاملاً تصادفی. فصلِ بعد کامل به این می‌پردازد و مهم‌ترین جملهٔ کلِ ترم را می‌گوید.

۵. نموداری که دروغ می‌گوید#

بیایید یک نمودارِ کاملاً درست و یک نمودارِ کاملاً گمراه‌کننده از یک داده بکشیم — کنارِ هم، تا تفاوت را در یک نگاه ببینی.

برای دو نمودار در یک تصویر، به شکلِ دومِ matplotlib نیاز داری. تا اینجا هر دستوری را روی «شکلِ فعال» می‌زدی (plt.title، plt.xlabel). حالا plt.subplots(1, 2) دو چیز برمی‌گرداند: خودِ شکل (fig) و آرایه‌ای از خانه‌های نموداری (axes) — اینجا دو خانه، در یک ردیف و دو ستون.

از آن به بعد به‌جای plt. روی هر خانه جداگانه دستور می‌دهی، و نامِ دستورها یک set_ جلو می‌گیرند: plt.title می‌شود axes[0].set_title، و plt.ylim می‌شود axes[0].set_ylim. همان کارها، فقط با آدرسِ مشخص. آن plt.tight_layout() هم آخرِ کار فاصله‌ها را مرتب می‌کند تا برچسب‌ها روی هم نیفتند.

fig, axes = plt.subplots(1, 2, figsize=(10, 4))

axes[0].bar(range(len(by_brand)), by_brand.values / 1e6)
axes[0].set_ylim(0, 50)
axes[0].set_title("honest: axis starts at zero")

axes[1].bar(range(len(by_brand)), by_brand.values / 1e6)
axes[1].set_ylim(22, 25)
axes[1].set_title("misleading: axis starts at 22")

for ax in axes:
    ax.set_xticks(range(len(by_brand)))
    ax.set_xticklabels(["Dell", "HP", "Lenovo", "Asus", "Apple"], rotation=30)
plt.tight_layout()
plt.show()

print("اختلاف واقعی دل تا ایسوس:", round((by_brand.iloc[3] - by_brand.iloc[0]) / 1e6, 1), "میلیون")
print("نسبت به میانگین کل:", round((by_brand.iloc[3] - by_brand.iloc[0]) / df["price"].mean() * 100, 1), "درصد")
اختلاف واقعی دل تا ایسوس: 2.4 میلیون
نسبت به میانگین کل: 9.2 درصد

نمودارِ سمتِ راست از ۲۲ شروع شده، پس اختلافِ ناچیزِ چهار برندِ اول را به کوهی از تفاوت تبدیل می‌کند. عددها یکی‌اند؛ فقط محور عوض شده.

اختلافِ واقعی ۲٫۴ میلیون است — حدودِ ۹ درصدِ میانگینِ کل. واقعی هست، ولی نه آن چیزی که نمودارِ راست نشان می‌دهد.

📏 اندازه بگیر: برای نمودارِ میله‌ای، محورِ عمودی باید از صفر شروع شود. طولِ میله همان چیزی است که مغز مقایسه می‌کند، و بریدنِ محور آن مقایسه را می‌شکند. (برای نمودارِ خطی این قاعده برقرار نیست — آنجا شیب مهم است نه طول.) هر وقت نموداری دیدی که تفاوتی را دراماتیک نشان می‌دهد، اول به عددهای محور نگاه کن.

۶. یک خطای واقعی: نمودار روی نمودارِ قبلی#

plt.plot(by_year.index, by_year.values / 1e6)
plt.plot(by_year.index, by_year.values / 2e6)
plt.title("two lines, one figure")
plt.show()

plt.figure()
plt.plot(by_year.index, by_year.values / 1e6)
plt.title("a fresh figure")
plt.show()
print("دو شکل جدا کشیده شد")
دو شکل جدا کشیده شد

matplotlib یک شکلِ فعال دارد و هر دستورِ رسم روی همان می‌نشیند. اگر plt.figure() نزنی، نمودارِ تازه‌ات روی قبلی سوار می‌شود.

در نوت‌بوک این یکی از گیج‌کننده‌ترین رفتارهاست: سلولی که دیروز یک خط می‌کشید، امروز سه خط دارد — چون سلول‌های دیگر را هم اجرا کرده‌ای. این دقیقاً همان حالتِ پنهانی است که در ترمِ ۱ فصلِ ۱۰ دیدی، این بار در matplotlib.

🔧 اگر کار نکرد: اگر نمودارت خط‌های اضافی دارد یا اصلاً ظاهر نمی‌شود: هر سلولِ نموداری را با plt.figure() شروع کن و با plt.show() تمام کن. و اگر باز هم عجیب بود، Restart and run all — همان راهِ حلِ همیشگی.

🤖 از دستیارت بپرس: «برای نشان‌دادنِ رابطهٔ یک متغیرِ دسته‌ای و یک متغیرِ عددی، چه نوع نمودارهایی هست و هرکدام چه چیزی را بهتر نشان می‌دهند؟» جوابش احتمالاً box plot و violin plot را می‌آورد که ما نساخته‌ایم. یکی‌شان را روی همین داده امتحان کن و ببین چه چیزی می‌گوید که نمودارِ میله‌ای نمی‌گفت.

واژه‌های تازهٔ این فصل#

کلمه تلفظ به حروف فارسی یعنی چه
matplotlib مت‌پلات‌لیب کتابخانهٔ پایهٔ رسمِ نمودار در پایتون
histogram هیستوگرام شمارشِ داده در بازه‌های مساوی؛ شکلِ توزیع را نشان می‌دهد
scatter اسکَتِر نمودارِ پراکندگی؛ هر ردیف یک نقطه
correlation کارِلِیشِن عددی بینِ ۱- و ۱+ که هم‌حرکتیِ دو متغیر را می‌گوید

تمرین‌ها

اول خودت فکر کن یا امتحان کن — بعد اینجا را باز کن.

در فصل بعد#

ابزارها را داری: خلاصه، گروه‌بندی، نمودار. فصلِ بعد آن‌ها را در یک فرآیند می‌چیند که اسمش تحلیلِ اکتشافی است، و اولین قانونِ دوره را کامل باز می‌کند: «اول داده را ببین». آنجا همبستگیِ ۰٫۶۳۵ این فصل را دوباره برمی‌داریم و می‌پرسیم واقعاً چه می‌گوید — و مهم‌تر، چه چیزی نمی‌گوید.

به آخر این فصل رسیدی!

اگر ساختی و جواب داد، این دکمه مال توست.