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

فصل ۳ از ۱۰

پیشرفت ترم
۰٪

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

`DataFrame`: جدولِ واقعی در پایتون

فصل ۳پیش‌نمایش رایگان
۱۰ دقیقه مطالعه فصل ۳

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

آرایه برای عددِ خالص عالی است، ولی دادهٔ واقعی فقط عدد نیست: ستونِ نام دارد، ستونِ شهر دارد، خانهٔ خالی دارد، و ستون‌هایش اسم دارند نه شماره. در این فصل pandas را می‌بینی و DataFrame را — ابزاری که در کلِ کارِ داده بیشترین استفاده را دارد. یک فایلِ واقعی با ۲۴۴ ردیف می‌خوانی و در همان ده دقیقهٔ اول سه مشکلِ جدی در آن پیدا می‌کنی، بدونِ اینکه هنوز یک خط پاک‌سازی نوشته باشی.

یک برگهٔ جدولِ برش‌خورده که سرستون‌هایش برجسته‌اند و یکی از ستون‌هایش کاملاً از بقیه بیرون کشیده شده

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

  • یک فایلِ CSV را بخوانی و در چند خط بفهمی داخلش چیست
  • تفاوتِ DataFrame و Series را بگویی
  • با info() و dtypes نوعِ هر ستون را ببینی و بفهمی چرا این اولین چیزی است که چک می‌کنی
  • با describe() و value_counts() اولین تصویرِ داده را بگیری

قبل از شروع#

از فصل ۱: shape و dtype و اینکه هر ستون یک نوع دارد.

از ترمِ ۱ فصل ۴: dict — چون یک DataFrame در ذهنت چیزی شبیهِ «دیکشنری‌ای از ستون‌ها» است.

سلولِ راه‌اندازی: بالای نوت‌بوک یک سلول هست که فایلِ laptops.csv را می‌سازد — دادهٔ ساختگی ولی با همان کثیفی‌هایی که دادهٔ واقعی دارد. یک بار اجرایش کن. ساختگی بودنش عمدی است: این‌طور نوت‌بوک همیشه و بدونِ اینترنت کار می‌کند و عددهای تو دقیقاً با کتاب می‌خوانند.

💡 نکته: آن سلول را باز کن و نگاهش کن، ولی اگر همه‌اش را نفهمیدی نگران نشو و رویش وقت نگذار. عمداً از ابزارهایی استفاده می‌کند که هنوز نرسیده‌ای — چند تایشان را در فصل‌های ۴ و ۵ همین ترم می‌بینی و بقیه را در ترمِ ۵. کارش فقط ساختنِ فایل است؛ درسِ این فصل از خطِ read_csv به بعد شروع می‌شود.

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

۱. خواندنِ فایل، و اولین نگاه#

import pandas as pd

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

print(df.shape)
print(list(df.columns))
print(df.head())
(244, 5)
['brand', 'city', 'year', 'ram_gb', 'price']
   brand    city  year  ram_gb       price
0    اپل  اصفهان  2021    32.0  58,500,000
1   لنوو    مشهد  2019    16.0  23,400,000
2    اپل   تهران  2024     8.0  54,200,000
3  ایسوس    مشهد  2023     8.0    32900000
4   لنوو   تهران  2019     8.0  20,500,000

read_csv یک فایلِ متنیِ ویرگول‌جدا را می‌خواند و به DataFrame تبدیل می‌کند. نامِ df یک قراردادِ جهانی است و در هر کدی که بخوانی همین را می‌بینی.

سه چیز را همین حالا ببین:

  • shape می‌گوید ۲۴۴ ردیف و ۵ ستون. عادت کن این اولین چیزی باشد که چاپ می‌کنی.
  • ستونِ سمتِ چپ که از صفر می‌شمارد، اندیس (index) است. جزوِ ستون‌ها نیست؛ شناسهٔ ردیف است.
  • و حالا به ستونِ price نگاه کن. بعضی مقدارها ویرگولِ هزارگان دارند و بعضی نه. این یک هشدارِ جدی است.

۲. Series در برابر DataFrame#

one_column = df["price"]
two_columns = df[["brand", "price"]]

print(type(one_column))
print(type(two_columns))
print(one_column.head(3))
<class 'pandas.Series'>
<class 'pandas.DataFrame'>
0    58,500,000
1    23,400,000
2    54,200,000
Name: price, dtype: str

دو تکه از این خروجی به نسخهٔ pandas بستگی دارند و ممکن است روی دستگاهِ تو کمی متفاوت باشند: مسیرِ کاملِ نوع (در نسخه‌های قدیمی‌تر <class 'pandas.core.series.Series'>) و نامِ نوعِ ستونِ متنی (در نسخه‌های قدیمی‌تر object به‌جای str). چیزی که در همهٔ نسخه‌ها یکی است، همین دو جمله است:

  • df["price"] با یک کروشه یک ستونِ تنها می‌دهد که نوعش Series است — یک آرایهٔ تک‌بعدی با اندیس و نام.
  • df[["brand", "price"]] با دو کروشه یک DataFrame می‌دهد. کروشهٔ داخلی در واقع یک list از نامِ ستون‌هاست.

این تفاوت را یاد بگیر، چون خیلی از خطاهای بعدی از همین می‌آید: بعضی توابع Series می‌خواهند و بعضی DataFrame.

چک کن: آن خطِ آخرِ خروجی — Name: price, dtype: ... — بخشی از خودِ Series است. اگر آن را ندیدی، احتمالاً دو کروشه گذاشته‌ای و DataFrame گرفته‌ای.

۳. dtypes: اولین چیزی که باید چک کنی#

print(df.dtypes)
brand         str
city          str
year        int64
ram_gb    float64
price         str
dtype: object

شکلِ دقیقِ این خروجی به نسخهٔ pandas بستگی دارد — در نسخه‌های قدیمی‌تر به‌جای str کلمهٔ object را می‌بینی. چیزی که مهم است و در همهٔ نسخه‌ها یکی است، این جمله است:

ستونِ price عدد نیست، متن است.

و این یعنی هیچ محاسبه‌ای رویش کار نمی‌کند. df["price"].mean() یا خطا می‌دهد یا — بدتر — چیزی می‌دهد که معنا ندارد. دلیلش را در بخشِ ۱ دیدی: آن ویرگول‌های هزارگان. read_csv وقتی می‌بیند بعضی مقدارها عدد نیستند، کلِ ستون را متن می‌کند و هیچ هشداری هم نمی‌دهد.

ram_gb هم float64 است، نه int64. چرا؟ چون خانه‌های خالی دارد و در pandas مقدارِ گم‌شده یک عددِ اعشاریِ خاص است، پس کلِ ستون اعشاری می‌شود.

⚠️ مواظب باش: این پرکاربردترین منبعِ باگِ خاموش در کارِ داده است. یک ستونِ «عددی» که در واقع متن است، در هر محاسبه‌ای نتیجهٔ غلط یا خطا می‌دهد — و اگر مرتب‌سازی کنی، "9,000,000" قبل از "58,500,000" می‌آید چون الفبایی مقایسه می‌شوند نه عددی. dtypes را همیشه، بلافاصله بعد از read_csv، نگاه کن.

۴. info(): همه‌چیز در یک نگاه#

df.info()
<class 'pandas.DataFrame'>
RangeIndex: 244 entries, 0 to 243
Data columns (total 5 columns):
 #   Column  Non-Null Count  Dtype
---  ------  --------------  -----
 0   brand   244 non-null    str
 1   city    244 non-null    str
 2   year    244 non-null    int64
 3   ram_gb  217 non-null    float64
 4   price   244 non-null    str
dtypes: float64(1), int64(1), str(3)
memory usage: 9.7 KB

ستونِ Non-Null Count مهم‌ترین بخشِ این خروجی است: چهار ستون ۲۴۴ مقدار دارند، ولی ram_gb فقط ۲۱۷ تا. یعنی ۲۷ خانهٔ خالی.

پس تا اینجا بدونِ نوشتنِ یک خط پاک‌سازی، دو مشکل پیدا کردی: ستونِ قیمت متن است، و ستونِ حافظه خانهٔ خالی دارد. سومی را بخشِ بعد نشان می‌دهد.

۵. describe() و value_counts(): اولین تصویر#

print(df.describe())
              year      ram_gb
count   244.000000  217.000000
mean   2020.663934   11.981567
std      12.202415    7.488240
min    1998.000000    4.000000
25%    2018.000000    8.000000
50%    2020.000000    8.000000
75%    2022.000000   16.000000
max    2205.000000   32.000000

describe() برای هر ستونِ عددی خلاصهٔ آماری می‌دهد. سه چیزِ گفتنی:

  • فقط دو ستون آمد، چون بقیه عددی نیستند. نبودنِ price در این جدول خودش یک یافته است.
  • به max ستونِ year نگاه کن: ۲۲۰۵. یک لپ‌تاپ از سالِ ۲۲۰۵. و min هم ۱۹۹۸ است. این سومین مشکل است — مقدارِ غیرممکن.
  • std ستونِ year برابرِ ۱۲٫۲ است، در حالی که ۷۵ درصدِ داده بینِ ۲۰۱۸ و ۲۰۲۲ است. یک مقدارِ پرتِ تنها، انحرافِ معیار را منفجر می‌کند.

🌱 ریشه‌اش کجاست: 25% و 50% و 75% را چارک می‌گویند: عددی که یک‌چهارم، نصف، و سه‌چهارمِ داده زیرِ آن است. 50% همان میانه است. اگر می‌خواهی بدانی چرا میانه در برابرِ مقدارِ پرت مقاوم است ولی میانگین نه — دقیقاً همان چیزی که در همین خروجی می‌بینی — ریشه، ترم ۶ فصل ۶ بازش می‌کند.

برای ستون‌های غیرعددی، ابزارِ درست value_counts است:

print(df["brand"].value_counts())
brand
ایسوس    67
لنوو     51
دل       46
اچ پی    36
اپل      28
ايسوس    12
اچ پي     4
Name: count, dtype: int64

هفت برند؟ ولی فقط پنج برند وجود دارد. با دقت نگاه کن: «ایسوس» و «ايسوس» دو ردیفِ جدا هستند، و همین‌طور «اچ پی» و «اچ پي». تفاوتشان یک حرف است که چشمِ تو آن را یکسان می‌بیند — «ی» فارسی در برابرِ «ي» عربی.

این چهارمین مشکل است، و بدترینشان: هیچ خطایی نمی‌دهد، در info() دیده نمی‌شود، و اگر بخواهی میانگینِ قیمت را برندبه‌برند حساب کنی، ۱۲ ردیفِ «ایسوس» در گروهِ اشتباه می‌روند.

📏 اندازه بگیر: توجه کن که این چهار مشکل را با چهار دستور پیدا کردیم: head، dtypes، info، value_counts. هیچ‌کدام هوشمندانه نبودند؛ فقط نگاه کردیم. این دقیقاً معنای قانونِ اولِ دوره است: «اول داده را ببین». کسی که مستقیم سراغِ مدل برود، هر چهارتای این‌ها را با خودش وارد مدل می‌کند و بعد ماه‌ها دنبالِ دلیلِ نتیجهٔ عجیب می‌گردد.

۶. یک خطای واقعی: محاسبه روی ستونِ متنی#

print(df["price"].mean())
TypeError: Could not convert string '58,500,000...' to numeric

دقیقاً همان چیزی که پیش‌بینی کردیم. pandas نمی‌تواند میانگینِ یک ستونِ متنی را بگیرد.

نکتهٔ آموزنده این است که این‌جا خوش‌شانس بودیم که خطا گرفتیم. حالتِ خطرناک‌تر وقتی است که عملیاتی روی متن هم تعریف شده باشد و بی‌سروصدا نتیجهٔ بی‌معنا بدهد:

print(df["price"].max())
print(df["price"].sort_values().head(3).tolist())
9100000
['10000000', '10000000', '11,200,000']

max جواب داد — و جوابش غلط است. گران‌ترین لپ‌تاپ ۹٫۱ میلیون نیست؛ در بخشِ ۱ یکی به قیمتِ ۵۸ میلیون دیدیم. ولی چون مقایسه الفبایی انجام شد، متنی که با 9 شروع می‌شود از متنی که با 5 شروع می‌شود بزرگ‌تر شد.

خروجیِ مرتب‌سازی هم همان‌قدر بی‌معناست: '11,200,000' بعد از '10000000' آمده، و ارزان‌ترین‌ها اصلاً این‌ها نیستند. دو مقدار که یکی ویرگول دارد و یکی ندارد، حتی با هم قابلِ مقایسه نیستند.

(آن .tolist() آخرِ خط کارِ مهمی نمی‌کند: ستون را به یک list سادهٔ پایتون تبدیل می‌کند تا سه مقدار در یک خط و کنارِ هم چاپ شوند، به‌جای شکلِ چندخطیِ Series.)

🔧 اگر کار نکرد: اگر جایی عددی گرفتی که با شهودت جور درنمی‌آید، اول dtype آن ستون را چاپ کن. یک ستونِ متنی که فکر می‌کنی عددی است، بی‌سروصداترین باگِ کلِ کارِ داده است. راهِ رفعش را در فصلِ ۵ کامل می‌بینی.

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

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

کلمه تلفظ به حروف فارسی یعنی چه
pandas پانداس کتابخانهٔ اصلیِ کار با دادهٔ جدولی در پایتون
DataFrame دیتافریم جدول: چند ستونِ نام‌دار با یک اندیسِ مشترک
Series سری‌ز یک ستونِ تنها؛ آرایهٔ تک‌بعدی با اندیس و نام
index ایندِکس شناسهٔ ردیف‌ها؛ جزوِ ستون‌ها نیست
value_counts ولیو کانتس شمارشِ تکرارِ هر مقدار در یک ستون

تمرین‌ها

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

در فصل بعد#

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

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

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