โมดูล 3/5 · สัปดาห์ 7–9 · 27 ชม.

ไฟล์ ข้อมูล และไลบรารี

UAT 104 การเขียนโปรแกรมคอมพิวเตอร์

เวลาเรียนประมาณ 90 นาทีร่าง รอตรวจปรับปรุงล่าสุด 26 กันยายน 2569

บทเรียน

เมื่อเรียนจบโมดูลนี้ ผู้เรียนจะสามารถ

  1. อ่านและเขียนไฟล์ CSV และ JSON โดยกำหนดการเข้ารหัสอักขระ UTF-8 ให้ถูกต้อง
  2. ใช้ pandas อ่าน สำรวจ และสรุปข้อมูล flight log
  3. ตรวจหาค่าหายและค่าผิดปกติ แล้วแก้ไขด้วย .loc ตามกติกา Copy-on-Write ของ pandas 3
  4. สรุปข้อมูลตามกลุ่มด้วย groupby และวาดกราฟด้วย matplotlib
  5. อธิบายว่าวิธีประมวลผลข้อมูลส่งผลต่อคำตอบอย่างไร

ความรู้พื้นฐานที่ควรมี: UAT 104 โมดูล 2

ทำไมต้องรู้

ข้อมูลจากโดรนจริงไม่เคยสะอาด เซนเซอร์กระตุกเป็นครั้งคราว สัญญาณ GPS อ่อนเมื่อใกล้อาคาร และบางแถวก็ไม่มีค่า ถ้านำข้อมูลดิบไปสรุปทันที จะได้ความสูงสูงสุด ระยะบิน หรือการใช้พลังงานที่ผิด และอาจนำไปสู่การตัดสินใจที่ผิด ทักษะสำคัญของนักเทคโนโลยีจึงไม่ใช่แค่คำนวณเป็น แต่คือ ตรวจข้อมูลก่อนเชื่อ

โมดูลนี้ใช้ไฟล์ flight_log_sample.csv ซึ่งเป็น ข้อมูลสังเคราะห์ จากแบบจำลองเที่ยวบินสำรวจ 403 วินาที บันทึก 2 ครั้งต่อวินาที และตั้งใจใส่ปัญหาไว้ให้ฝึกหา สร้างซ้ำได้ด้วย make_sample_data.py

ห้าขั้นตอน อ่านไฟล์ด้วย read_csv สำรวจด้วย info และ describe ทำความสะอาดค่าหายและค่าผิดปกติ วิเคราะห์ด้วย groupby และสรุป และทำกราฟและรายงานด้วย matplotlib ด้านล่างเขียนว่าบันทึกทุกขั้นเป็นโค้ดเพื่อให้คนอื่นรันซ้ำได้ผลเดิม
ภาพที่ 1 ท่อประมวลผลข้อมูล

อ่านและเขียนไฟล์

CSV และปัญหาภาษาไทย

ถ้าไม่ระบุการเข้ารหัสอักขระ Python รุ่นก่อน 3.15 จะใช้ค่าตามภาษาของเครื่อง ซึ่งบน Windows ภาษาไทยอาจเป็น code page 874 ไฟล์ที่เขียนบนเครื่องหนึ่งจึงอาจอ่านเป็นตัวอักษรเพี้ยนบนอีกเครื่อง PEP 686 จะเปลี่ยนค่าเริ่มต้นเป็น UTF-8 ตั้งแต่ Python 3.15 แต่ระหว่างนี้ ให้ระบุ encoding="utf-8" ทุกครั้ง และเปิดไฟล์ CSV ด้วย newline="" ตามเอกสารของโมดูล csv

import csv

with open("flight_log_sample.csv", encoding="utf-8", newline="") as f:
    rows = list(csv.DictReader(f))

print(len(rows), "rows")
print(rows[0]["t_s"], rows[0]["alt_m"], rows[0]["mode"])
print(type(rows[0]["alt_m"]))
807 rows
0.0 -0.06 GUIDED
<class 'str'>

สังเกตว่าโมดูล csv คืนทุกค่าเป็นข้อความ (str) ต้องแปลงเป็นตัวเลขเอง ซึ่งเป็นเหตุผลหนึ่งที่งานวิเคราะห์นิยมใช้ pandas

JSON สำหรับผลสรุป

JSON เหมาะกับการเก็บผลสรุปหรือการตั้งค่าที่มีโครงสร้าง ใช้ ensure_ascii=False เพื่อให้ภาษาไทยในไฟล์อ่านได้ ไม่ถูกแปลงเป็นรหัส ส...

import json
from pathlib import Path

summary = {"flight": "สำรวจแปลง A", "rows": len(rows)}
path = Path("summary.json")
path.write_text(json.dumps(summary, ensure_ascii=False, indent=2), encoding="utf-8")

print(path.read_text(encoding="utf-8"))
print(json.loads(path.read_text(encoding="utf-8"))["flight"])
{
  "flight": "สำรวจแปลง A",
  "rows": 807
}
สำรวจแปลง A

สำรวจข้อมูลด้วย pandas

pandas เก็บข้อมูลตารางในรูป DataFrame แต่ละคอลัมน์เป็น Series และแต่ละแถวมี index กำกับ วิชานี้ใช้ pandas 3 ซึ่งต้องใช้ Python 3.11 ขึ้นไป

ตาราง DataFrame มีหัวคอลัมน์ t_s alt_m batt_v และ mode คอลัมน์ซ้ายสุดเป็น index 0 1 240 และ 400 คอลัมน์ alt_m ถูกเน้นเป็น Series แถว 240 มีความสูง 69.94 และแถว 400 มีค่าแรงดันเป็น NaN
ภาพที่ 2 โครงสร้างของ DataFrame
import pandas as pd

df = pd.read_csv("flight_log_sample.csv")
print(df.shape)
print(df[["t_s", "alt_m", "batt_v", "gps_sats", "mode"]].head(3))
print(df.isna().sum()[lambda s: s > 0])
(807, 10)
   t_s  alt_m  batt_v  gps_sats    mode
0  0.0  -0.06   25.15        14  GUIDED
1  0.5  -0.06   25.15        14  GUIDED
2  1.0  -0.16   25.18        14  GUIDED
batt_v    2
dtype: int64

แถวแรก ๆ ยืนยันว่าอ่านไฟล์ถูก และ isna() บอกว่ามีแรงดันแบตเตอรี่หายไป 2 แถว ขั้นต่อไปคือดูภาพรวมของตัวเลข

print(df[["alt_m", "groundspeed_ms", "batt_v"]].describe().round(2))
        alt_m  groundspeed_ms  batt_v
count  807.00          807.00  805.00
mean    28.15            7.47   24.28
std      6.76            2.77    0.27
min     -0.24            0.00   23.85
25%     29.86            7.35   24.05
50%     29.97            8.85   24.27
75%     30.09            9.01   24.48
max     69.94            9.55   25.20

ค่า max ของความสูงคือ 69.94 m ทั้งที่แผนบินที่ 30 m และค่ากลาง (50%) อยู่ราว 30 m นี่คือสัญญาณของค่าผิดปกติที่ต้องตรวจก่อนรายงาน

ทำความสะอาดข้อมูล

ตัวอย่างที่ 1 หาและแก้ค่าความสูงที่กระโดด

ใช้ ค่ามัธยฐานเคลื่อนที่ 5 จุด เป็นค่าอ้างอิงของแต่ละจุด ค่าที่ห่างจากค่าอ้างอิงเกิน 5 m ถือว่าผิดปกติ มัธยฐานเหมาะกว่าค่าเฉลี่ย เพราะค่าผิดปกติค่าเดียวไม่ดึงมัธยฐานไปด้วย

df["alt_ref"] = df["alt_m"].rolling(5, center=True, min_periods=1).median()
spike = (df["alt_m"] - df["alt_ref"]).abs() > 5
print(df.loc[spike, ["t_s", "alt_m", "alt_ref"]])

df.loc[spike, "alt_m"] = df.loc[spike, "alt_ref"]
print("max altitude after cleaning:", df["alt_m"].max())
       t_s  alt_m  alt_ref
240  120.0  69.94    30.08
max altitude after cleaning: 30.45
กราฟความสูงเทียบเวลาตั้งแต่ 0 ถึง 403 วินาที โดรนไต่จากพื้นถึงราว 30 เมตรภายในประมาณ 20 วินาที บินคงที่ราว 30 เมตร แล้วลงจอดหลังวินาทีที่ 386 เส้นข้อมูลดิบสีชมพูมีค่ากระโดดถึงราว 70 เมตรที่วินาทีที่ 120 ส่วนเส้นหลังทำความสะอาดสีกรมท่าไม่มีค่ากระโดด
ภาพที่ 3 ความสูงก่อนและหลังทำความสะอาด (กราฟจากโค้ดในบทเรียน)

pandas 3: แก้ค่าด้วย .loc เท่านั้น

pandas 3 เปิดใช้ Copy-on-Write เป็นค่าเริ่มต้น การเขียนแบบต่อสาย เช่น df[spike]["alt_m"] = 0 จะไม่เปลี่ยนข้อมูลใน df อีกต่อไป (pandas จะเตือน) ให้เขียน df.loc[เงื่อนไข, "คอลัมน์"] = ค่า ในคำสั่งเดียวเสมอ ตำราหลายเล่มรวมถึงฉบับที่ 3 ของ McKinney เขียนก่อน pandas 3 จึงอาจยังใช้แบบเก่า

ค่าหาย ไม่ควรเติมด้วยการเดา ถ้าจำเป็นต้องเติม ต้องบอกวิธีที่ใช้ เช่น ประมาณค่าระหว่างจุดข้างเคียงด้วย interpolate() และบันทึกว่าเติมกี่แถว

สรุปตามกลุ่ม

groupby แยกข้อมูลตามค่าของคอลัมน์หนึ่ง แล้วสรุปแต่ละกลุ่ม ข้อมูลบันทึกทุก 0.5 s จำนวนแถวคูณ 0.5 จึงเป็นเวลาที่อยู่ในแต่ละโหมด

per_mode = df.groupby("mode").agg(
    samples=("t_s", "size"),
    mean_speed_ms=("groundspeed_ms", "mean"),
)
per_mode["minutes"] = per_mode["samples"] * 0.5 / 60
print(per_mode.round(2).sort_values("minutes", ascending=False))
        samples  mean_speed_ms  minutes
mode
AUTO        659           8.24     5.49
RTL          70           8.50     0.58
GUIDED       44           0.00     0.37
LAND         34           0.00     0.28

วิธีคิดเปลี่ยนคำตอบ

ตัวอย่างที่ 2 เที่ยวบินนี้บินไกลเท่าไร

รวมระยะระหว่างจุด GPS ที่ติดกันด้วยสูตร haversine (โมดูล 2) แต่เขียนแบบเวกเตอร์ด้วย NumPy ให้คำนวณทั้งคอลัมน์พร้อมกัน แล้วเทียบหลายวิธี ในฟังก์ชันต้องแปลงค่าเป็น NumPy array ด้วย np.asarray() ก่อน เพราะถ้าตัดช่วงบน pandas Series ตรง ๆ pandas จะจับคู่ข้อมูลตาม index ทำให้คำนวณผิดหรือเกิดข้อผิดพลาด (บั๊กนี้เกิดขึ้นจริงระหว่างเขียนบทเรียนนี้)

import numpy as np


def path_length_m(lat_deg, lon_deg, radius=6_371_000.0):
    """Sum of haversine distances between consecutive points."""
    lat = np.radians(np.asarray(lat_deg, dtype=float))
    lon = np.radians(np.asarray(lon_deg, dtype=float))
    a = np.sin(np.diff(lat) / 2) ** 2 + np.cos(lat[:-1]) * np.cos(lat[1:]) * np.sin(np.diff(lon) / 2) ** 2
    return float((2 * radius * np.arcsin(np.sqrt(a))).sum())


print(f"raw fixes:       {path_length_m(df['lat_deg'], df['lon_deg']):.0f} m")

weak = df["gps_sats"] < 10
fixed = df[["lat_deg", "lon_deg"]].copy()
fixed.loc[weak, ["lat_deg", "lon_deg"]] = np.nan
fixed = fixed.interpolate()
print(f"weak GPS filled: {path_length_m(fixed['lat_deg'], fixed['lon_deg']):.0f} m  ({weak.sum()} fixes)")

for window in (3, 5, 9):
    smooth = fixed.rolling(window, center=True, min_periods=1).mean()
    print(f"smoothed ({window}):    {path_length_m(smooth['lat_deg'], smooth['lon_deg']):.0f} m")
raw fixes:       3092 m
weak GPS filled: 3067 m  (10 fixes)
smoothed (3):    3014 m
smoothed (5):    2991 m
smoothed (9):    2951 m

เส้นทางตามแผนของแบบจำลองยาว 3 015 m ข้อมูลดิบให้ ยาวเกินจริง เพราะสัญญาณรบกวนทำให้ตำแหน่งแกว่งไปมา และช่วง GPS อ่อน 10 จุดทำให้แกว่งมากที่สุด การปรับให้เรียบช่วยได้ แต่ถ้าหน้าต่างกว้างเกินไปจะ ตัดมุม ที่จุดเลี้ยว ได้ระยะสั้นเกินจริง

ไม่มีวิธีใดถูกเสมอ หลักคือ รายงานวิธีคู่กับผลทุกครั้ง และตรวจกับค่าที่รู้ ในงานจริงอาจไม่มีเส้นทางจริงให้เทียบ แต่ยังเทียบกับแผนบินหรือกับระยะจากเซนเซอร์อื่นได้

วาดกราฟ

matplotlib เป็นไลบรารีกราฟพื้นฐานของ Python บันทึกกราฟเป็นไฟล์ด้วย savefig() เพื่อใส่ในรายงาน ภาพที่ 3 สร้างจากโค้ดแบบเดียวกันนี้

import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(9, 3.4))
ax.plot(df["t_s"], df["alt_m"], label="cleaned")
ax.set_xlabel("time (s)")
ax.set_ylabel("altitude (m)")
ax.grid(alpha=0.3)
ax.legend()
fig.tight_layout()
fig.savefig("altitude.png", dpi=110)
print("saved altitude.png")
saved altitude.png

ปฏิบัติการประจำโมดูล

ปฏิบัติการ: รายงานคุณภาพข้อมูลเที่ยวบิน

ใช้ไฟล์ flight_log_sample.csv ทำใน Jupyter Notebook

  1. อ่านไฟล์ ตรวจค่าหาย ค่าผิดปกติของความสูง และช่วงที่ดาวเทียม GPS น้อยกว่า 10 ดวง แล้วรายงานเป็นตาราง
  2. ทำความสะอาดด้วย .loc และเปรียบเทียบ describe() ก่อนและหลัง
  3. สรุปเวลาและความเร็วเฉลี่ยของแต่ละโหมดการบิน และหาเวลาที่แบตเตอรี่ลดถึง 90% ครั้งแรก
  4. คำนวณระยะบินอย่างน้อยสองวิธี อธิบายว่าเลือกวิธีใดเพราะอะไร
  5. วาดกราฟความสูงและแรงดันแบตเตอรี่เทียบเวลา แล้วส่ง notebook ที่ รันใหม่ตั้งแต่ต้นได้ผลเดิม

ข้อผิดพลาดที่พบบ่อย

ระวัง

  • ไม่ระบุ encoding="utf-8" จนภาษาไทยเพี้ยนบนเครื่องอื่น
  • รายงานค่า max หรือ min จากข้อมูลดิบ โดยไม่ตรวจค่าผิดปกติ
  • แก้ค่าแบบต่อสาย ซึ่งไม่ได้ผลใน pandas 3
  • ลบแถวที่มีปัญหาทิ้งเงียบ ๆ โดยไม่บันทึกว่าลบกี่แถวเพราะอะไร
  • ปรับข้อมูลให้เรียบมากเกินไป จนเสียรายละเอียดจริง
  • แก้ไฟล์ข้อมูลต้นฉบับ แทนที่จะเก็บต้นฉบับไว้และบันทึกผลเป็นไฟล์ใหม่

สรุป

  • ระบุ encoding="utf-8" ทุกครั้งที่อ่านเขียนไฟล์ข้อความ และใช้ ensure_ascii=False กับ JSON ที่มีภาษาไทย
  • pandas ใช้ read_csv, head, isna, describe, groupby สำรวจและสรุปข้อมูล
  • ค่ามัธยฐานเคลื่อนที่ช่วยหาค่าผิดปกติ และใน pandas 3 ต้องแก้ค่าด้วย .loc
  • วิธีประมวลผลเปลี่ยนคำตอบ ต้องรายงานวิธีคู่กับผล
  • บันทึกกราฟด้วย savefig() และทำ notebook ให้รันซ้ำได้ผลเดิม

แบบฝึกตรวจความเข้าใจ

  1. ทำไมต้องระบุ encoding="utf-8" เมื่อเปิดไฟล์ใน Python 3.14 บน Windows ภาษาไทย
  2. โมดูล csv คืนค่าในคอลัมน์ alt_m เป็นชนิดใด
  3. ทำไมใช้มัธยฐานเคลื่อนที่แทนค่าเฉลี่ยเคลื่อนที่เพื่อหาค่ากระโดด
  4. เขียนคำสั่ง pandas 3 ที่ตั้งค่า batt_v เป็น 0 เฉพาะแถวที่ mode เป็น "LAND"
  5. ข้อมูลบันทึกทุก 0.5 s มีแถวในโหมด RTL 70 แถว อยู่ในโหมดนี้กี่วินาที
เฉลย
  1. ก่อน Python 3.15 ค่าเริ่มต้นขึ้นกับภาษาของเครื่อง ซึ่งอาจเป็น code page 874 ไม่ใช่ UTF-8 ไฟล์จึงอาจอ่านเพี้ยน
  2. str (ข้อความ) ต้องแปลงเป็นตัวเลขเอง
  3. เพราะค่าผิดปกติค่าเดียวดึงค่าเฉลี่ยให้เพี้ยนไปด้วย แต่แทบไม่ทำให้มัธยฐานเปลี่ยน
  4. df.loc[df["mode"] == "LAND", "batt_v"] = 0
  5. s

สรุปสูตรสำคัญ

ค่ามัธยฐานเคลื่อนที่ (หน้าต่าง 5 จุด)
ระยะทางรวมจากจุด GPS

แหล่งอ้างอิงหลัก

  1. Python Software Foundation. The Python tutorial (Python 3.14 documentation). link
  2. Inada, N. (2022). PEP 686 – Make UTF-8 mode default. link
  3. The pandas development team. (2026). What’s new in 3.0.0. pandas documentation. link
  4. The Matplotlib development team. Matplotlib documentation (3.11). link
  5. McKinney, W. (2022). Python for data analysis (3rd ed.). O'Reilly. link
  6. Downey, A. B. (2024). Think Python: How to think like a computer scientist (3rd ed.). O’Reilly. link

อ่านเพิ่มเติม

ศึกษาหน่วยความรู้ที่กำหนดล่วงหน้า ดูสื่อประกอบ และทำ quiz ประจำโมดูล

ในชั้นเรียน / ภาคสนาม

ปฏิบัติการในห้องแล็บหรือภาคสนามตามใบงาน พร้อม checklist ความปลอดภัย

หลักฐานการเรียนรู้: ใบงานที่ผ่านการตรวจและผล quiz

แบบทดสอบประจำโมดูล

แบบทดสอบนี้ใช้ตรวจความเข้าใจ (formative) ไม่ใช่การสอบเก็บคะแนน

โดเมนความรู้: การเขียนโปรแกรมและเทคโนโลยีดิจิทัล · คณิตศาสตร์ ฟิสิกส์ และสถิติ