ข้ามไปยังเนื้อหา
Tayakorn

ทำไมตาถึงอ่านกราฟนี้ได้เร็วกว่า

LEVEL 0 · ปูพื้นจากศูนย์

ช่องทางเข้ารหัสมีกี่แบบ

ถ้าขั้นเข้ารหัสคือขั้นเดียวที่คุณคุมได้ คำถามต่อไปคือ คุณมีอะไรให้เลือกใช้บ้าง

คำตอบคือน้อยกว่าที่คิดมาก — คุณสมบัติที่ตาถอดค่าเชิงปริมาณกลับมาได้จริง ๆ มีอยู่ราวเจ็ดอย่างเท่านั้น และแต่ละอย่างมีราคาต่างกัน

4.1 เจ็ดช่องทาง

แต่ละช่องทางข้างล่างตอบสามคำถามเหมือนกันหมด: (ก) มันคืออะไร (ข) ตาถอดได้แม่นแค่ไหน (ค) เหมาะกับข้อมูลชนิดไหน

ช่องทางคืออะไรความแม่นเหมาะกับ
ตำแหน่งบนแกนร่วมจุดที่วางบนแกนเดียวกัน เทียบกันตรง ๆสูงสุดค่าเชิงปริมาณทุกชนิด
ความยาวความสูงหรือความยาวของแท่งจากฐานเดียวกันสูงค่าเชิงปริมาณ
ทิศทาง / ความชันความเอียงของเส้นกลางอัตราการเปลี่ยนแปลง
มุมขนาดมุมของชิ้นวงกลมต่ำสัดส่วนหยาบ ๆ เท่านั้น
พื้นที่ขนาดของวงกลมหรือสี่เหลี่ยมต่ำค่าที่ต่างกันหลายเท่า
ความเข้มอ่อน-เข้มของสีเดียวกันต่ำค่าเชิงลำดับ
สี (hue)สีที่ต่างชนิดกัน เช่น ฟ้ากับส้มใช้บอกปริมาณไม่ได้การแบ่งกลุ่ม

สองแถวสุดท้ายเป็นที่มาของความผิดพลาดที่พบบ่อยที่สุดในงานจริง — การเอาสีมาแบกตัวเลข ซึ่งเราจะเจาะลงไปทั้งบทที่ 8

4.2 ค่าชุดเดียวกัน ย้ายช่องทางแล้วเกิดอะไรขึ้น

คำว่า "แม่นกว่า" จะยังลอยอยู่จนกว่าจะเห็นตัวเลข ลองดูข้อมูลชุดเดียวกันบนสองช่องทาง

เข้ารหัสด้วยความยาว2218251619ต่าง 1 จุด = 4.00% ของช่วง — ตาแยกออกเข้ารหัสด้วยมุม79.2°64.8°90.0°57.6°68.4°ต่าง 1 จุด = 1.00% ของวง — ตาแยกไม่ออก
FIG 4.1 ค่าชุดเดียวกัน เข้ารหัสคนละช่องทาง · หน่วย จ (19) กับ ข (18) ต่างกัน 1 จุดเท่ากันทั้งสองภาพ แต่บนแท่งความต่างนั้นกิน 4.00% ของช่วงที่ใช้ได้ ส่วนบนวงกลมกินแค่ 1.00% — แท่งขยายความต่างเดียวกันให้ใหญ่ขึ้น 4 เท่า ก่อนส่งถึงตา (คำนวณจาก encode.py ไม่ได้กะเอา)

หน่วย จ มีค่า 19 หน่วย ข มีค่า 18 ต่างกัน 1 จุด เท่ากันทั้งสองภาพ แต่

  • บนแท่ง ความต่างนั้นกลายเป็นระยะที่กิน 4.00% ของช่วงทั้งหมดที่แท่งใช้ได้
  • บนวงกลม ความต่างเดียวกันกลายเป็นมุม 3.6 องศา ซึ่งกินแค่ 1.00% ของวงกลม

แท่งขยายความต่างเดียวกันให้ใหญ่ขึ้น 4 เท่าก่อนส่งถึงตา โดยที่ตัวเลขต้นทางไม่ขยับเลยสักตัว นี่คือความหมายของคำว่า "ย้ายช่องทาง"

สังเกตว่าทั้งสองภาพเรียงลำดับเหมือนกันคือ ก ข ค ง จ ตามตารางต้นทาง จึงเทียบกันได้ตรง ๆ · ส่วนเรื่องที่ว่าของสองชิ้นอยู่ติดกันหรืออยู่คนละมุมมีผลต่อการเปรียบเทียบแค่ไหน เป็นคนละปัจจัยกับความแม่นของช่องทาง และรอไว้ที่บทที่ 11

และนี่คือคำตอบแรกของปริศนาข้อที่หนึ่งที่ปลูกไว้ในบทที่ 1 — กราฟวงกลมไม่ได้โดนด่าเพราะมันน่าเกลียด มันโดนด่าเพราะมันบีบความต่างให้เล็กลงสี่เท่าก่อนที่ตาจะได้เห็น

4.3 กับดักของพื้นที่

การเข้ารหัสด้วยพื้นที่มีกับดักที่แยกออกมาเป็นเรื่องของตัวเอง เพราะมันผิดได้ทั้งที่ตั้งใจดี

ถูก — พื้นที่แปรตามค่าr = รากที่สองของสัดส่วน100r = 40ตาอ่านได้ 100 — ตรงค่าจริง25r = 20ตาอ่านได้ 25 — ตรงค่าจริงผิด — รัศมีแปรตามค่าr = สัดส่วนตรง ๆ100r = 40ตาอ่านได้ 10025r = 10ตาอ่านได้ 6.25
FIG 4.2 กับดักที่พบบ่อยที่สุดของการเข้ารหัสด้วยพื้นที่ — ตาอ่านพื้นที่ ไม่ได้อ่านรัศมี · ทั้งสองคู่มีค่าเท่ากันคือ 100 กับ 25 · คู่ขวาปรับรัศมีตามค่าตรง ๆ ผลคือวงที่มีค่าจริง 25 ถูกตาอ่านเป็น 6.25 เหลือแค่หนึ่งในสี่ของความจริง · ยิ่งค่าในชุดข้อมูลต่างกันมาก ความเพี้ยนยิ่งบานปลาย

เวลาเราบอกว่า "ทำวงกลมให้ใหญ่ตามค่า" คนส่วนใหญ่ไปปรับ รัศมี ตามค่าตรง ๆ ซึ่งฟังดูสมเหตุสมผล แต่ตาไม่ได้อ่านรัศมี ตาอ่าน พื้นที่ และพื้นที่แปรตามรัศมียกกำลังสอง

ผลคือค่าเล็ก ๆ ถูกกดให้ดูเล็กกว่าความจริงมาก หน่วย ง ที่มีค่าจริง 16 ถูกตาอ่านเป็น 10.24 คือต่ำกว่าความเป็นจริง 36%

💡 วิธีแก้คือปรับรัศมีด้วยรากที่สองของค่า r = sqrt(v / v_max) * r_max ไม่ใช่ r = v / v_max * r_max · เครื่องมือทำกราฟหลายตัวยังตั้งค่าเริ่มต้นเป็นแบบหลังอยู่ ให้ตรวจก่อนใช้เสมอ

4.4 สคริปต์ที่คำนวณทุกตัวเลขในบทนี้

ไม่มีตัวเลขไหนในสองภาพข้างบนที่กะด้วยตา ทั้งหมดมาจากสคริปต์นี้ — เล่มนี้เรียกมันว่า encode.py และ caption ของ FIG 4.1 อ้างถึงไฟล์นี้ · รันได้ด้วย Python มาตรฐาน ไม่ต้องลงอะไรเพิ่ม

"""คำนวณเรขาคณิตของกราฟชุดเดียวกันที่เข้ารหัสหลายแบบ — Python มาตรฐานล้วน"""
import math

# ชุดข้อมูลสมมติ: สัดส่วนการใช้จ่ายของ 5 หน่วยงาน (รวม 100)
DATA = [("", 22), ("", 18), ("", 25), ("", 16), ("", 19)]
vals = [v for _, v in DATA]
total = sum(vals)

order = sorted(DATA, key=lambda kv: -kv[1])
gap_val = min(order[i][1] - order[i + 1][1] for i in range(len(order) - 1))

# --- 1. ความยาว (แท่ง) : ค่า -> พิกเซล เชิงเส้น ---
BAR_MAX_PX = 110
print("=== เข้ารหัสด้วยความยาว (แท่ง) ===")
for k, v in DATA:
    print(f"  {k}: {v:>3} -> ยาว {v / max(vals) * BAR_MAX_PX:7.2f} px")
gap_len = gap_val / max(vals) * BAR_MAX_PX
print(f"  คู่ที่สูสีที่สุด (ต่าง {gap_val} จุด) ห่างกัน {gap_len:.2f} px "
      f"= {gap_len / BAR_MAX_PX * 100:.2f}% ของช่วงที่แท่งใช้ได้")

# --- 2. มุม (วงกลม) : ค่า -> องศา ---
print("\n=== เข้ารหัสด้วยมุม (วงกลม) ===")
start, R = -90.0, 90.0          # เริ่มที่ 12 นาฬิกา
for k, v in DATA:
    sweep = v / total * 360.0
    end = start + sweep
    x1, y1 = R * math.cos(math.radians(start)), R * math.sin(math.radians(start))
    x2, y2 = R * math.cos(math.radians(end)), R * math.sin(math.radians(end))
    print(f"  {k}: {v:>3} -> {sweep:6.2f} องศา | "
          f"M 0 0 L {x1:7.2f} {y1:7.2f} A {R} {R} 0 0 1 {x2:7.2f} {y2:7.2f} Z")
    start = end
gap_deg = gap_val / total * 360.0
print(f"  คู่ที่สูสีที่สุด (ต่าง {gap_val} จุด) ห่างกัน {gap_deg:.2f} องศา "
      f"= {gap_deg / 360 * 100:.2f}% ของช่วงที่วงกลมใช้ได้")

# --- 3. พื้นที่ (ฟอง) : ถูก = รากที่สอง, ผิด = เชิงเส้น ---
print("\n=== เข้ารหัสด้วยพื้นที่ (ฟอง) ===")
R_MAX = 28.0
for k, v in DATA:
    r_ok = math.sqrt(v / max(vals)) * R_MAX      # พื้นที่แปรตามค่า (ถูก)
    r_bad = v / max(vals) * R_MAX                # รัศมีแปรตามค่า (ผิด)
    area_bad = (r_bad / R_MAX) ** 2 * max(vals)  # ค่าที่ตา "อ่านได้" จากรูปที่ผิด
    print(f"  {k}: {v:>3} -> r ถูก {r_ok:5.2f} | r ผิด {r_bad:5.2f} "
          f"-> ตาอ่านได้ {area_bad:5.2f} (คลาดเคลื่อน {area_bad - v:+.2f})")

pct_bar, pct_pie = gap_len / BAR_MAX_PX * 100, gap_deg / 360 * 100
print(f"\n= แท่งขยายความต่างเดียวกันให้ใหญ่ขึ้น {pct_bar / pct_pie:.1f} เท่า")

# --- 4. คู่ที่ต่างกันมาก เพื่อให้เห็นความเพี้ยนของพื้นที่ชัด ๆ ---
print("\n=== คู่เทียบ 25 กับ 100 (ต่างกัน 4 เท่า) ===")
PAIR, R_BIG = [25, 100], 40.0
for v in PAIR:
    r_ok = math.sqrt(v / max(PAIR)) * R_BIG
    r_bad = v / max(PAIR) * R_BIG
    read = (r_bad / R_BIG) ** 2 * max(PAIR)
    print(f"  ค่า {v:>4} -> r ถูก {r_ok:5.2f} | r ผิด {r_bad:5.2f} "
          f"-> ตาอ่านได้ {read:6.2f} (เหลือ {read / v * 100:.0f}% ของค่าจริง)")

ผลรันจริง

=== เข้ารหัสด้วยความยาว (แท่ง) ===
  ก:  22 -> ยาว   96.80 px
  ข:  18 -> ยาว   79.20 px
  ค:  25 -> ยาว  110.00 px
  ง:  16 -> ยาว   70.40 px
  จ:  19 -> ยาว   83.60 px
  คู่ที่สูสีที่สุด (ต่าง 1 จุด) ห่างกัน 4.40 px = 4.00% ของช่วงที่แท่งใช้ได้

=== เข้ารหัสด้วยมุม (วงกลม) ===
  ก:  22 ->  79.20 องศา | M 0 0 L    0.00  -90.00 A 90.0 90.0 0 0 1   88.41  -16.86 Z
  ข:  18 ->  64.80 องศา | M 0 0 L   88.41  -16.86 A 90.0 90.0 0 0 1   52.90   72.81 Z
  ค:  25 ->  90.00 องศา | M 0 0 L   52.90   72.81 A 90.0 90.0 0 0 1  -72.81   52.90 Z
  ง:  16 ->  57.60 องศา | M 0 0 L  -72.81   52.90 A 90.0 90.0 0 0 1  -83.68  -33.13 Z
  จ:  19 ->  68.40 องศา | M 0 0 L  -83.68  -33.13 A 90.0 90.0 0 0 1   -0.00  -90.00 Z
  คู่ที่สูสีที่สุด (ต่าง 1 จุด) ห่างกัน 3.60 องศา = 1.00% ของช่วงที่วงกลมใช้ได้

=== เข้ารหัสด้วยพื้นที่ (ฟอง) ===
  ก:  22 -> r ถูก 26.27 | r ผิด 24.64 -> ตาอ่านได้ 19.36 (คลาดเคลื่อน -2.64)
  ข:  18 -> r ถูก 23.76 | r ผิด 20.16 -> ตาอ่านได้ 12.96 (คลาดเคลื่อน -5.04)
  ค:  25 -> r ถูก 28.00 | r ผิด 28.00 -> ตาอ่านได้ 25.00 (คลาดเคลื่อน +0.00)
  ง:  16 -> r ถูก 22.40 | r ผิด 17.92 -> ตาอ่านได้ 10.24 (คลาดเคลื่อน -5.76)
  จ:  19 -> r ถูก 24.41 | r ผิด 21.28 -> ตาอ่านได้ 14.44 (คลาดเคลื่อน -4.56)

= แท่งขยายความต่างเดียวกันให้ใหญ่ขึ้น 4.0 เท่า

=== คู่เทียบ 25 กับ 100 (ต่างกัน 4 เท่า) ===
  ค่า   25 -> r ถูก 20.00 | r ผิด 10.00 -> ตาอ่านได้   6.25 (เหลือ 25% ของค่าจริง)
  ค่า  100 -> r ถูก 40.00 | r ผิด 40.00 -> ตาอ่านได้ 100.00 (เหลือ 100% ของค่าจริง)

ตัวเลข 4.00% กับ 1.00% ไม่ได้ขึ้นกับขนาดภาพที่เราเลือก ลองเปลี่ยน BAR_MAX_PX เป็นค่าอะไรก็ได้แล้วรันใหม่ เปอร์เซ็นต์จะเท่าเดิม เพราะมันเป็นสัดส่วนของช่วงที่ช่องทางนั้นมีให้ใช้ ไม่ใช่ของจอ

4.5 ยังตอบไม่ได้ว่าแม่นกว่า "เท่าไหร่"

สิ่งที่บทนี้ทำได้คือบอกว่าช่องทางไหนบีบหรือขยายความต่าง ซึ่งเป็นเรขาคณิตล้วน ๆ คำนวณได้จากหน้ากระดาษ

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

เปลี่ยนช่องทาง เท่ากับเปลี่ยนความแม่นที่คนอ่านจะได้ โดยที่ตัวเลขต้นทางไม่ขยับเลยสักตัว

อ่านแบบเต็มเล่ม