ทำไมตาถึงอ่านกราฟนี้ได้เร็วกว่า
กราฟที่ดีไม่ได้มาจากรสนิยม แต่มาจากขีดจำกัดของระบบสายตาที่วัดเป็นตัวเลขได้ อ่านจบแล้วมองกราฟตรงหน้าออกเองว่ามันจะถูกอ่านผิดตรงไหน และแก้ได้โดยไม่ต้องถามใคร
คู่มือที่พาคุณจากคนที่ทำกราฟด้วยความรู้สึก ไปจนตัดสินใจได้ด้วยเหตุผลที่ตรวจสอบได้ — ไม่ใช่เพราะจำกฎได้มากขึ้น แต่เพราะเข้าใจว่าระหว่างตัวเลขในตารางกับข้อสรุปในหัวคนฟัง มันเกิดอะไรขึ้นบ้าง และตรงไหนที่ข้อมูลหายไป
- ระดับ: เริ่มจากศูนย์ (ไม่ต้องรู้สถิติ ไม่ต้องเขียนโปรแกรมเป็น)
- เวลาอ่าน: ~170 นาที ตลอดสิบห้าบท
- ภาษา: ไทย พร้อมสคริปต์ที่รันได้ด้วย Python มาตรฐาน ไม่ต้องลงไลบรารีเพิ่มสักตัว
LEVEL 0 · ปูพื้นจากศูนย์
สไลด์ที่ต้องอธิบายปากเปล่าทุกครั้ง#
คุณเคยยืนอยู่หน้าห้องประชุม มีกราฟขึ้นจอ แล้วได้ยินตัวเองพูดประโยคนี้ไหม
"ดูตรงแท่งนี้นะครับ อันนี้คือหน่วยที่ใช้เยอะที่สุด ส่วนอันนี้..."
ถ้าเคย นั่นคืออาการ ไม่ใช่ความบังเอิญ กราฟที่ต้องมีคนยืนอธิบายทุกครั้ง คือกราฟที่ยังไม่ได้ทำงานของมัน — และมันจะออกไปนั่งอยู่ในอีเมลกับไฟล์แนบโดยไม่มีคุณไปด้วย
สิ่งที่เกิดขึ้นเกือบทุกครั้งหลังจากนั้นคือ เราไปแก้ที่ "ความสวย" เปลี่ยนชุดสี เพิ่มเงา จัดตำแหน่งใหม่ แล้วรอบหน้าก็ยังต้องยืนอธิบายเหมือนเดิม เพราะปัญหาไม่เคยอยู่ตรงนั้น
1.1 ข้อมูลชุดเดียวกัน สองชะตากรรม
ลองดูภาพข้างล่างนี้ ตัวเลขในสองแผงเหมือนกันทุกตัว ไม่มีการปัดเศษ ไม่มีการตัดข้อมูลออก
แผงซ้ายไม่ได้ผิดอะไรเลยสักจุด ทุกแท่งสูงถูกต้อง ทุกตัวเลขตรงกับตาราง ถ้าเอาไปให้ตรวจ มันผ่าน
แต่มันโยนงานสามอย่างไปให้คนดูทำแทน — เรียงลำดับเอง จับคู่แท่งกับชื่อหน่วยเอง และสรุปเอง ส่วนแผงขวาทำสามอย่างนั้นให้เสร็จก่อนที่คนดูจะเงยหน้าขึ้นมา
กราฟไม่ได้ล้มเหลวตอนคุณทำมัน มันล้มเหลวตอนตาคนอื่นอ่านมัน
1.2 เล่มนี้จะไม่ให้กฎ 10 ข้อกับคุณ
คู่มือเรื่องนี้ส่วนใหญ่จะบอกคุณว่า "เรียงข้อมูลเสมอ" "อย่าใช้กราฟวงกลม" "ลบเส้นตารางออก" ซึ่งถูกทุกข้อ แต่กฎมีปัญหาอยู่อย่างเดียว คือมันไม่บอกว่าตัวเองใช้ไม่ได้เมื่อไหร่ พอเจอสถานการณ์ที่กฎเงียบ คุณก็กลับไปเดาเหมือนเดิม
เล่มนี้เลือกอีกทาง — ลงไปดูชั้นที่อยู่ใต้กฎพวกนั้น คือ กลไกการรับรู้ของระบบสายตามนุษย์ ซึ่งมีขีดจำกัดที่วัดออกมาเป็นตัวเลขได้ พอคุณเห็นกลไก กฎทั้งหมดจะกลายเป็นเรื่องที่คุณอนุมานเองได้ รวมถึงกฎที่ไม่มีใครเคยเขียนไว้
1.3 ปริศนาสามข้อที่ปลูกไว้ตั้งแต่ตอนนี้
ระหว่างทางคุณจะเจอคำถามสามข้อนี้ และเล่มนี้จะไขทีละข้อ
| ปริศนา | ไขที่ |
|---|---|
| ทำไมกราฟวงกลมถึงโดนด่าตลอด ทั้งที่ตาก็อ่านมันออก | บทที่ 4 · 5 · 6 |
| ทำไมสไลด์ที่สวยกว่า กลับสื่อสารได้แย่กว่า | บทที่ 7 |
| ถ้าตาถอดค่าจากกราฟได้ไม่แม่น แล้วเราจะเชื่อกราฟไปทำไม | บทที่ 10 |
ข้อแรกจะเริ่มมีคำตอบตั้งแต่บทที่ 4 และจะชัดขึ้นเรื่อย ๆ อย่าเพิ่งข้ามไป
ตาเห็นก่อนที่สมองจะคิด#
บทนี้ปลดล็อกข้อเท็จจริงหนึ่งที่ฟังดูเหลือเชื่อ — มีงานสายตาบางประเภทที่ใช้เวลาเท่าเดิม ไม่ว่าข้อมูลบนหน้าจะมี 160 ชิ้นหรือ 1,600 ชิ้น
ถ้าคุณใช้มันเป็น คุณจะหยุดขอให้คนดู "ช่วยดูตรงนี้หน่อย" ไปตลอดกาล
2.1 ลองพิสูจน์ด้วยตาเอง
ในภาพข้างล่างมีตัวเลข 160 ตัว ซ่อนเลข 3 ไว้ 6 ตัว ลองหาจากแผงบนให้ครบก่อน แล้วค่อยเลื่อนลงไปดูแผงล่าง อย่าเพิ่งแอบดู
3 ไว้ 6 ตัวเท่ากัน · แผงบนคุณต้องไล่อ่านทีละตัว เวลาที่ใช้แปรตามจำนวนตัวเลข · แผงล่างทั้งหกตัวผุดขึ้นพร้อมกันโดยคุณไม่ได้ตั้งใจหา และต่อให้เพิ่มเป็น 1,600 ตัวก็ยังเร็วเท่าเดิมแผงบนคุณต้องกวาดสายตาไปทีละตัว ถ้ามีเวลาจับ คุณจะพบว่าเวลาที่ใช้แปรตามจำนวนตัวเลขบนหน้าเกือบเป็นเส้นตรง เพิ่มตัวเลขเป็นสองเท่า เวลาก็เพิ่มราวสองเท่า
แผงล่างไม่ใช่แบบนั้น ทั้งหกตัวปรากฏขึ้นพร้อมกัน ก่อนที่คุณจะตั้งใจหาด้วยซ้ำ และต่อให้เพิ่มตัวเลขรอบ ๆ เป็นหมื่นตัว มันก็ยังผุดขึ้นเร็วเท่าเดิม
2.2 กลไก: สายตามีสองโหมด ไม่ใช่โหมดเดียว
ความแตกต่างข้างบนไม่ได้เกิดจาก "ตั้งใจมากขึ้น" แต่เกิดจากการที่สมองใช้คนละกลไกกันไปเลย
- โหมดไล่ทีละชิ้น — ใช้ความสนใจ (attention) ซึ่งเป็นทรัพยากรจำกัด ตรวจได้ทีละจุด เวลาที่ใช้แปรตามจำนวนชิ้น
- โหมดขนาน — ระบบสายตาประมวลผลทั้งหน้าพร้อมกันในระดับที่ยังไม่ต้องใช้ความสนใจ แล้วส่งผลลัพธ์ขึ้นมาให้รู้ตัวทีเดียว เวลาที่ใช้เกือบไม่ขึ้นกับจำนวนชิ้น
💡 preattentive attributes — คุณสมบัติทางภาพที่ระบบสายตาประมวลผลได้ก่อนที่ความสนใจจะไปถึง คำว่า pre-attentive แปลตรงตัวว่า "ก่อนการใส่ใจ" งานคลาสสิกที่ทำให้แนวคิดนี้เป็นที่รู้จักคือชุดการทดลองของ Anne Treisman ในทศวรรษ 1980
ตัวเลขที่งานสายนี้มักอ้างถึงคือราว 200 ถึง 250 มิลลิวินาที ซึ่งพอ ๆ กับการจ้องหนึ่งครั้ง แปลว่าผลลัพธ์มาถึงคุณก่อนที่จะได้กวาดสายตาไปหา พูดอีกอย่างคือ คุณเห็นมันโดยไม่ได้มอง
2.3 อะไรบ้างที่เข้าโหมดขนานได้
ไม่ใช่ทุกคุณสมบัติจะทำงานแบบนี้ ที่ใช้ได้จริงในงานกราฟมีอยู่ไม่กี่อย่าง
| คุณสมบัติ | ใช้ได้ | หมายเหตุ |
|---|---|---|
| สี (hue) | ได้ดีมาก | แรงที่สุดเมื่อมีสีเดียวที่ต่างจากพวก · ยิ่งจำนวนสีมาก การเด้งยิ่งอ่อนลงเรื่อย ๆ |
| ขนาด | ได้ | ต้องต่างพอสมควร ไม่ใช่ต่าง 5% |
| ความเข้ม | ได้ | อ่อนกว่าสีนิดหน่อย |
| ทิศทาง / ความเอียง | ได้ | ใช้ได้ดีกับเส้นแนวโน้ม |
| ตำแหน่งที่หลุดจากแนว | ได้ | จุดที่ออกนอกกลุ่มเด้งขึ้นมาเอง |
| รูปทรง | ได้แย่ | สามเหลี่ยมในกองสี่เหลี่ยมต้องไล่หา |
| สองคุณสมบัติพร้อมกัน | ไม่ได้ | "วงกลมสีแดง" ในกองที่มีทั้งวงกลมสีน้ำเงินและสี่เหลี่ยมสีแดง ต้องไล่ทีละตัว |
แถวสุดท้ายสำคัญที่สุด และเป็นเหตุผลว่าทำไมกราฟที่ใช้ทั้งสีและรูปทรงแยกหลายมิติพร้อมกันถึงอ่านช้ากว่าที่คนทำคาดไว้เสมอ
2.4 สร้างตารางของคุณเอง
ตารางในภาพไม่ได้วาดด้วยมือ มันมาจากสคริปต์นี้ (เรียกว่า preattentive.py ในเล่มนี้) ซึ่งใช้ random.seed ตายตัว ผลลัพธ์จึงเหมือนกันทุกครั้งที่รัน
"""สร้างกองตัวเลขสำหรับการทดลอง 'หาเลข 3' — seed คงที่ ผลลัพธ์เหมือนกันทุกครั้ง"""
import random
ROWS, COLS, TARGET, N_TARGET = 8, 20, 3, 6
random.seed(1984) # ปีที่ Cleveland กับ McGill ตีพิมพ์งานที่เราจะเจอในบทที่ 5
others = [d for d in range(10) if d != TARGET]
grid = [[random.choice(others) for _ in range(COLS)] for _ in range(ROWS)]
spots = random.sample([(r, c) for r in range(ROWS) for c in range(COLS)], N_TARGET)
for r, c in spots:
grid[r][c] = TARGET
print(f"ตาราง {ROWS}x{COLS} = {ROWS * COLS} ตัว · ซ่อนเลข {TARGET} ไว้ {N_TARGET} ตัว")
print("ตำแหน่งเป้าหมาย (แถว,คอลัมน์ เริ่มที่ 0):", sorted(spots))
print()
for row in grid:
print(" ".join(str(d) for d in row))ผลรันจริง
ตาราง 8x20 = 160 ตัว · ซ่อนเลข 3 ไว้ 6 ตัว
ตำแหน่งเป้าหมาย (แถว,คอลัมน์ เริ่มที่ 0): [(0, 19), (1, 17), (2, 14), (3, 1), (4, 8), (6, 9)]
6 8 6 5 4 7 1 4 5 0 2 1 9 7 6 4 2 7 2 3
6 9 9 5 5 8 7 6 8 4 9 6 9 9 2 9 9 3 6 1
7 1 7 2 2 0 8 6 4 9 9 7 5 0 3 2 5 7 1 8
4 3 1 8 5 4 1 4 0 6 4 4 0 8 7 2 8 4 1 0
7 5 9 1 1 0 5 4 3 6 7 0 4 2 4 7 5 4 0 6
0 0 2 5 4 7 1 1 6 6 1 5 5 0 6 9 5 5 6 7
2 7 2 7 8 7 1 2 5 3 1 2 7 0 6 0 1 1 1 9
0 9 5 4 1 7 6 6 5 5 8 6 6 8 9 5 4 9 4 1ลองเปลี่ยน ROWS เป็น 24 แล้วรันใหม่ ถ้าคุณพิมพ์ผลออกมาเป็นข้อความล้วนแบบนี้ เวลาหาจะเพิ่มขึ้นชัดเจน แต่ถ้าคุณระบายสีเลข 3 ในโปรแกรมที่ระบายสีได้ เวลาจะไม่ขยับ นั่นคือเส้นแบ่งของสองโหมด
2.5 ทำไมคำอธิบายสัญลักษณ์ถึงเป็นศัตรู
กลับไปดู FIG 1.1 อีกครั้ง แผงซ้ายมีคำอธิบายสัญลักษณ์อยู่ด้านล่าง ซึ่งบังคับให้คนดูจับคู่ "แท่งที่สาม" กับ "หน่วย ค" ด้วยการกวาดสายตาไปกลับ
การจับคู่แบบนั้นคืองานโหมดไล่ทีละชิ้นเต็มตัว และคนดูต้องทำห้ารอบ ส่วนแผงขวาที่ติดป้ายไว้ใต้แท่งเลย ตัดงานนั้นทิ้งทั้งหมด
ถ้าคนดูต้อง "หา" อะไรสักอย่าง แปลว่าคุณยังไม่ได้เข้ารหัสสิ่งที่สำคัญลงไปในภาพ
แบบจำลองความคิด: เข้ารหัส–ถอดรหัส#
บทนี้สำคัญที่สุดในเล่ม ทุกบทที่เหลือคือการลงรายละเอียดของภาพเดียวที่จะเห็นข้างล่างนี้ ถ้าจำได้บทเดียว ให้จำบทนี้
3.1 สายพานสี่ขั้นที่เกิดขึ้นทุกครั้งที่มีคนดูกราฟ
ระหว่าง "ตัวเลขในตาราง" กับ "ข้อสรุปที่เขาเดินออกจากห้องไปพร้อมกับมัน" มีสี่ขั้นคั่นอยู่ และมีขั้นเดียวเท่านั้นที่เป็นของคุณ
- ตัวเลขจริง — สิ่งที่คุณมีในมือ
- เข้ารหัส — คุณเลือกว่าจะแปลงตัวเลขเป็นคุณสมบัติทางกายภาพอะไร ความยาวของแท่ง มุมของชิ้นวงกลม พื้นที่ของฟอง ความเข้มของสี นี่คือขั้นเดียวที่คุณควบคุมได้
- ถอดรหัส — ระบบสายตาของคนอ่านแปลงคุณสมบัติทางกายภาพนั้นกลับเป็นตัวเลขโดยประมาณ คุณสั่งไม่ได้ ขอร้องไม่ได้ และไม่มีทางรู้ว่าเขาถอดออกมาได้เท่าไหร่
- ข้อสรุป — สิ่งที่เขาเชื่อและเอาไปตัดสินใจต่อ
คุณเป็นคนเข้ารหัส แต่ตาคนอื่นเป็นคนถอดรหัส และคุณสั่งมันไม่ได้
3.2 ทำไม "ทำให้สวยขึ้น" ถึงไม่เคยแก้ปัญหา
พอเห็นสายพานแล้ว เรื่องที่ค้างมาตั้งแต่บทที่ 1 จะกระจ่างขึ้นทันที
การเปลี่ยนชุดสี เพิ่มเงา ปรับฟอนต์ ทั้งหมดนี้เกิดขึ้นที่ขั้น 2 ก็จริง แต่มันไปแตะคุณสมบัติที่ไม่ได้แบกข้อมูล ความสูญเสียที่ขั้น 3 จึงเท่าเดิมเป๊ะ กราฟสวยขึ้นแต่ถอดรหัสได้แม่นเท่าเดิม
การเปลี่ยนจากวงกลมเป็นแท่งไม่ใช่เรื่องรสนิยม มันคือการย้ายข้อมูลไปช่องทางที่ตาถอดรหัสได้แม่นกว่า ซึ่งเป็นคนละเรื่องกับความสวยโดยสิ้นเชิง
3.3 คำถามที่แบบจำลองนี้ทำให้ถามได้
นี่คือประโยชน์จริงของการมีแบบจำลองในหัว — มันเปลี่ยนคำถามที่คุณถามตัวเองเวลาทำกราฟ
| คำถามแบบเดิม | คำถามหลังมีแบบจำลอง |
|---|---|
| กราฟนี้สวยไหม | ข้อมูลของฉันถูกเข้ารหัสด้วยคุณสมบัติอะไร |
| ควรใช้กราฟแบบไหนดี | ช่องทางไหนที่ตาถอดรหัสได้แม่นที่สุดสำหรับข้อมูลชนิดนี้ |
| ใส่สีอะไรดี | สีกำลังแบกข้อมูลอยู่หรือแค่ตกแต่ง |
| ทำไมเขาไม่เข้าใจ | เขาถอดรหัสได้คลาดจากค่าจริงเท่าไหร่ |
คำถามคอลัมน์ขวาตอบได้ด้วยเหตุผล ส่วนคอลัมน์ซ้ายตอบได้ด้วยความรู้สึกเท่านั้น
บทถัดไปจะไล่ดูว่าช่องทางเข้ารหัสมีอะไรบ้าง แล้วบทที่ 5 จะให้ตัวเลขว่าแต่ละช่องทางสูญเสียเท่าไหร่ จากการทดลองที่วัดกับคนจริง
ช่องทางเข้ารหัสมีกี่แบบ#
ถ้าขั้นเข้ารหัสคือขั้นเดียวที่คุณคุมได้ คำถามต่อไปคือ คุณมีอะไรให้เลือกใช้บ้าง
คำตอบคือน้อยกว่าที่คิดมาก — คุณสมบัติที่ตาถอดค่าเชิงปริมาณกลับมาได้จริง ๆ มีอยู่ราวเจ็ดอย่างเท่านั้น และแต่ละอย่างมีราคาต่างกัน
4.1 เจ็ดช่องทาง
แต่ละช่องทางข้างล่างตอบสามคำถามเหมือนกันหมด: (ก) มันคืออะไร (ข) ตาถอดได้แม่นแค่ไหน (ค) เหมาะกับข้อมูลชนิดไหน
| ช่องทาง | คืออะไร | ความแม่น | เหมาะกับ |
|---|---|---|---|
| ตำแหน่งบนแกนร่วม | จุดที่วางบนแกนเดียวกัน เทียบกันตรง ๆ | สูงสุด | ค่าเชิงปริมาณทุกชนิด |
| ความยาว | ความสูงหรือความยาวของแท่งจากฐานเดียวกัน | สูง | ค่าเชิงปริมาณ |
| ทิศทาง / ความชัน | ความเอียงของเส้น | กลาง | อัตราการเปลี่ยนแปลง |
| มุม | ขนาดมุมของชิ้นวงกลม | ต่ำ | สัดส่วนหยาบ ๆ เท่านั้น |
| พื้นที่ | ขนาดของวงกลมหรือสี่เหลี่ยม | ต่ำ | ค่าที่ต่างกันหลายเท่า |
| ความเข้ม | อ่อน-เข้มของสีเดียวกัน | ต่ำ | ค่าเชิงลำดับ |
| สี (hue) | สีที่ต่างชนิดกัน เช่น ฟ้ากับส้ม | ใช้บอกปริมาณไม่ได้ | การแบ่งกลุ่ม |
สองแถวสุดท้ายเป็นที่มาของความผิดพลาดที่พบบ่อยที่สุดในงานจริง — การเอาสีมาแบกตัวเลข ซึ่งเราจะเจาะลงไปทั้งบทที่ 8
4.2 ค่าชุดเดียวกัน ย้ายช่องทางแล้วเกิดอะไรขึ้น
คำว่า "แม่นกว่า" จะยังลอยอยู่จนกว่าจะเห็นตัวเลข ลองดูข้อมูลชุดเดียวกันบนสองช่องทาง
encode.py ไม่ได้กะเอา)หน่วย จ มีค่า 19 หน่วย ข มีค่า 18 ต่างกัน 1 จุด เท่ากันทั้งสองภาพ แต่
- บนแท่ง ความต่างนั้นกลายเป็นระยะที่กิน 4.00% ของช่วงทั้งหมดที่แท่งใช้ได้
- บนวงกลม ความต่างเดียวกันกลายเป็นมุม 3.6 องศา ซึ่งกินแค่ 1.00% ของวงกลม
แท่งขยายความต่างเดียวกันให้ใหญ่ขึ้น 4 เท่าก่อนส่งถึงตา โดยที่ตัวเลขต้นทางไม่ขยับเลยสักตัว นี่คือความหมายของคำว่า "ย้ายช่องทาง"
สังเกตว่าทั้งสองภาพเรียงลำดับเหมือนกันคือ ก ข ค ง จ ตามตารางต้นทาง จึงเทียบกันได้ตรง ๆ · ส่วนเรื่องที่ว่าของสองชิ้นอยู่ติดกันหรืออยู่คนละมุมมีผลต่อการเปรียบเทียบแค่ไหน เป็นคนละปัจจัยกับความแม่นของช่องทาง และรอไว้ที่บทที่ 11
และนี่คือคำตอบแรกของปริศนาข้อที่หนึ่งที่ปลูกไว้ในบทที่ 1 — กราฟวงกลมไม่ได้โดนด่าเพราะมันน่าเกลียด มันโดนด่าเพราะมันบีบความต่างให้เล็กลงสี่เท่าก่อนที่ตาจะได้เห็น
4.3 กับดักของพื้นที่
การเข้ารหัสด้วยพื้นที่มีกับดักที่แยกออกมาเป็นเรื่องของตัวเอง เพราะมันผิดได้ทั้งที่ตั้งใจดี
เวลาเราบอกว่า "ทำวงกลมให้ใหญ่ตามค่า" คนส่วนใหญ่ไปปรับ รัศมี ตามค่าตรง ๆ ซึ่งฟังดูสมเหตุสมผล แต่ตาไม่ได้อ่านรัศมี ตาอ่าน พื้นที่ และพื้นที่แปรตามรัศมียกกำลังสอง
ผลคือค่าเล็ก ๆ ถูกกดให้ดูเล็กกว่าความจริงมาก หน่วย ง ที่มีค่าจริง 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
เปลี่ยนช่องทาง เท่ากับเปลี่ยนความแม่นที่คนอ่านจะได้ โดยที่ตัวเลขต้นทางไม่ขยับเลยสักตัว
LEVEL 1 · พื้นฐานที่ใช้ทุกวัน
ลำดับความแม่น — การทดลองที่วัดกับคนจริง#
บทที่แล้วจบลงด้วยคำถามค้าง เรขาคณิตบอกได้ว่าช่องทางไหนบีบความต่าง ช่องทางไหนขยาย แต่มันตอบไม่ได้ว่า คนจริงถอดค่าออกมาผิดพลาดเท่าไหร่
บทนี้คือบทที่คำถามนั้นได้คำตอบ และคำตอบไม่ได้มาจากการนั่งคิด แต่มาจากการจับคนมาวัด
5.1 จุดที่เรขาคณิตมองไม่เห็น
ลองใช้เรขาคณิตล้วน ๆ ตัดสินดูก่อนว่าช่องทางไหนดีกว่ากัน สคริปต์นี้ (channels.py) เอาชุดข้อมูลเดิมมาไล่ทุกช่องทาง แล้วบอกว่าความต่างที่แคบที่สุดในชุดจะได้ส่วนแบ่งเท่าไหร่
"""ตรวจชุดข้อมูลของคุณเองกับทุกช่องทาง — วัดเฉพาะเรขาคณิต ไม่ได้วัดสายตาคน"""
DATA = [("ก", 22), ("ข", 18), ("ค", 25), ("ง", 16), ("จ", 19)]
K = 0.05 # เกณฑ์: ความต่างต้องกินอย่างน้อยกี่ส่วนของช่วง ถึงจะหวังให้ตาแยกออก
def share_of_range(gap, vals, channel):
"""ความต่าง gap หน่วย ได้ส่วนแบ่งเท่าไหร่ของช่วงที่ช่องทางนั้นมีให้ใช้"""
hi, total = max(vals), sum(vals)
if channel in ("ตำแหน่งบนแกนร่วม", "ความยาว"):
return gap / hi # ทั้งคู่ยืดตามค่าเชิงเส้นเหมือนกัน
if channel == "มุม":
return gap / total # วงกลมต้องหารด้วยผลรวม ไม่ใช่ค่าสูงสุด
if channel == "พื้นที่":
lo = hi - gap
return (hi ** 0.5 - lo ** 0.5) / hi ** 0.5 # ตาอ่านพื้นที่ จึงคิดที่รากที่สอง
raise ValueError(channel)
vals = [v for _, v in DATA]
ranked = sorted(DATA, key=lambda kv: -kv[1])
gaps = [(ranked[i][0], ranked[i + 1][0], ranked[i][1] - ranked[i + 1][1])
for i in range(len(ranked) - 1)]
print("ช่องว่างระหว่างอันดับที่ติดกัน")
for a, b, g in gaps:
print(f" {a} - {b} = {g}")
gap = min(g for _, _, g in gaps)
print(f"\nช่องว่างที่แคบที่สุด = {gap} หน่วย")
print("ถ้าช่องทางไหนทำช่องว่างนี้ให้เห็นไม่ได้ กราฟนั้นก็บอกลำดับไม่ครบ\n")
print(f"{'ช่องทาง':<20}{'ส่วนแบ่งของช่วง':>18} {'ผล (เกณฑ์ ' + str(int(K * 100)) + '%)'}")
for ch in ("ตำแหน่งบนแกนร่วม", "ความยาว", "มุม", "พื้นที่"):
share = share_of_range(gap, vals, ch)
print(f"{ch:<20}{share * 100:>15.2f}% {'ผ่าน' if share >= K else 'ไม่ผ่าน'}")
print("\nสองข้อที่ตารางนี้บอกไม่ได้:")
print("1. ตำแหน่งกับความยาวได้เท่ากันเป๊ะ เพราะเรขาคณิตแยกสองอย่างนี้ไม่ออก")
print(" ที่แยกออกคือการวัดกับคนจริง ซึ่งเป็นคนละเครื่องมือ")
print("2. เกณฑ์ K ไม่ใช่ค่าคงที่ของธรรมชาติ มันขึ้นกับงานและขนาดของภาพ")ผลรันจริง
ช่องว่างระหว่างอันดับที่ติดกัน
ค - ก = 3
ก - จ = 3
จ - ข = 1
ข - ง = 2
ช่องว่างที่แคบที่สุด = 1 หน่วย
ถ้าช่องทางไหนทำช่องว่างนี้ให้เห็นไม่ได้ กราฟนั้นก็บอกลำดับไม่ครบ
ช่องทาง ส่วนแบ่งของช่วง ผล (เกณฑ์ 5%)
ตำแหน่งบนแกนร่วม 4.00% ไม่ผ่าน
ความยาว 4.00% ไม่ผ่าน
มุม 1.00% ไม่ผ่าน
พื้นที่ 2.02% ไม่ผ่าน
สองข้อที่ตารางนี้บอกไม่ได้:
1. ตำแหน่งกับความยาวได้เท่ากันเป๊ะ เพราะเรขาคณิตแยกสองอย่างนี้ไม่ออก
ที่แยกออกคือการวัดกับคนจริง ซึ่งเป็นคนละเครื่องมือ
2. เกณฑ์ K ไม่ใช่ค่าคงที่ของธรรมชาติ มันขึ้นกับงานและขนาดของภาพผลรันนี้มีของแถมสองอย่างที่ไม่ได้ตั้งใจให้เป็นบทเรียน แต่มันเป็นบทเรียนที่ดีกว่าที่ตั้งใจไว้
อย่างแรก ตำแหน่งบนแกนร่วมกับความยาวได้ 4.00% เท่ากันเป๊ะ เพราะทั้งคู่ยืดตามค่าแบบเชิงเส้นเหมือนกัน เรขาคณิตจึงไม่มีทางบอกได้ว่าอันไหนดีกว่า ทั้งที่ในชีวิตจริงมันไม่เท่ากัน
อย่างที่สอง ชุดข้อมูลนี้ ไม่ผ่านสักช่องทางเดียว เพราะช่องว่างที่แคบที่สุดคือ 1 หน่วย ซึ่งเล็กเกินไปสำหรับทุกช่องทาง เราจะกลับมาที่ข้อนี้ในหัวข้อ 5.4
5.2 การทดลองปี 1984
ปี 1984 William Cleveland กับ Robert McGill ตีพิมพ์งานที่กลายเป็นจุดอ้างอิงของสายนี้ วิธีของเขาเรียบง่ายมาก
ให้ผู้เข้าร่วมดูกราฟหนึ่งภาพ ชี้ไปที่สององค์ประกอบในภาพนั้น แล้วถามคำถามเดียว — ชิ้นที่เล็กกว่าเป็นกี่เปอร์เซ็นต์ของชิ้นที่ใหญ่กว่า จากนั้นเก็บคำตอบไปเทียบกับค่าจริง
พอทำซ้ำกับกราฟหลายชนิดที่เข้ารหัสด้วยช่องทางต่างกัน ก็ได้สิ่งที่เรขาคณิตให้ไม่ได้ คือ ความคลาดเคลื่อนเฉลี่ยของมนุษย์ต่อช่องทาง แล้วเอามาเรียงลำดับได้
💡 ทำไมเล่มนี้ไม่พิมพ์ตัวเลขความคลาดเคลื่อนของงานนั้น — เพราะเราไม่ได้วัดเอง และการทดลองกับคนก็ไม่ใช่สิ่งที่รันซ้ำได้บนโน้ตบุ๊ก เล่มนี้จึงหยิบมาแค่ อันดับ ซึ่งเป็นข้อค้นพบที่ยืนยันซ้ำแล้วในงานรุ่นหลัง · กติกาของเล่มนี้คือ ตัวเลขที่คำนวณเองได้ ต้องมาจากสคริปต์ที่คุณรันตรวจได้ทุกตัว ส่วนค่าที่มาจากงานวิจัยจะบอกที่มาไว้เสมอ — และในเล่มนี้มีอยู่สองที่เท่านั้นคือบทนี้กับเรื่องความไวของสายตาในบทที่ 2 และ 8
5.3 บันไดเจ็ดขั้น
สองขั้นบนสุดชนะเพราะเหตุผลเดียวกัน คือมี เส้นอ้างอิงร่วม ให้ตาใช้ พอทุกจุดวางอยู่บนแกนเดียวกัน การเทียบก็กลายเป็นการดูว่าอันไหนอยู่สูงกว่า ซึ่งเป็นงานที่ระบบสายตาทำได้แม่นมาก
มุมแพ้เพราะสองอย่างพร้อมกัน — ไม่มีเส้นอ้างอิงให้เทียบ และตาคนไวกับมุมที่ใกล้แนวตั้งหรือแนวนอนมากกว่ามุมเฉียง ความแม่นจึงไม่สม่ำเสมอแม้แต่ภายในวงกลมเดียวกัน ชิ้นที่วางอยู่ตรงหกนาฬิกากับชิ้นที่วางเฉียง ๆ ถูกอ่านด้วยความแม่นไม่เท่ากัน ทั้งที่มุมเท่ากัน
พื้นที่กับสีอยู่ท้ายบันไดไม่ได้แปลว่าห้ามใช้ มันแปลว่า อย่าเอาไปแบกตัวเลขที่คนต้องอ่านให้ตรง ใช้บอกกลุ่ม บอกคร่าว ๆ บอกว่า "อันนี้เยอะกว่ามาก" ได้สบาย
5.4 เมื่อคำตอบคืออย่าทำกราฟ
กลับไปที่ผลรันในหัวข้อ 5.1 ชุดข้อมูลของเราไม่ผ่านสักช่องทาง เพราะมีคู่ที่ต่างกันแค่ 1 หน่วย
สิ่งที่คนส่วนใหญ่ทำตอนเจอแบบนี้คือไล่เปลี่ยนชนิดกราฟไปเรื่อย ๆ หวังว่าจะเจออันที่ "เห็นชัดกว่า" แต่บันไดบอกเราแล้วว่าเพดานอยู่ตรงไหน — ถ้าช่องทางที่แม่นที่สุดยังทำไม่ได้ ก็ไม่มีช่องทางไหนทำได้
ทางออกจริงมีสามทาง และไม่มีทางไหนเป็นการเปลี่ยนชนิดกราฟ
| ทางออก | ทำยังไง | เหมาะเมื่อ |
|---|---|---|
| พิมพ์ตัวเลขไปเลย | ใส่ค่าที่แท่ง หรือทำเป็นตาราง | ความต่าง 1 หน่วยนั้นสำคัญจริง |
| เปลี่ยนสิ่งที่วัด | แสดงส่วนต่างจากค่าเป้าหมาย แทนค่าดิบ | สิ่งที่คนอ่านสนใจคือส่วนต่าง ไม่ใช่ระดับ |
| ยอมรับว่ามันเท่ากัน | จัดกลุ่มเป็น "พอ ๆ กัน" แล้วเน้นที่ต่างจริง | ความต่าง 1 หน่วยไม่มีความหมายในทางปฏิบัติ |
ทางที่สองคือทางที่คนมองข้ามบ่อยที่สุด ถ้าค่าจริงคือ 100 กับ 102 แต่เป้าหมายคือ 100 ทั้งคู่ การพล็อต "ส่วนต่างจากเป้า" จะได้ 0 กับ 2 ซึ่งช่องว่างกลายเป็น 100% ของช่วงทันที ข้อมูลชุดเดิม คำถามใหม่ ช่องว่างใหม่
5.5 บันไดนี้ไม่ใช่คำสั่ง
ก่อนจบบท มีข้อจำกัดที่ต้องพูดให้ชัด บันไดนี้วัดงานอย่างเดียว คือการอ่านค่าให้ตรง
ถ้าเป้าหมายของภาพเปลี่ยนไป ลำดับก็อาจเปลี่ยนตาม — จะหาค่าผิดปกติในข้อมูลหมื่นแถว สีบน heatmap ชนะขาดทั้งที่อยู่ขั้นล่างสุด จะดูว่ารูปร่างการกระจายเป็นยังไง ตำแหน่งชนะแต่ด้วยเหตุผลคนละข้อกับที่บันไดบอก
อันดับนี้ไม่ใช่ความเห็น มันคือผลการวัด — แต่มันวัดงานเดียว คือการอ่านค่าให้ตรง
นี่คือเหตุผลที่เล่มนี้ยืนยันจะสอนกลไกแทนที่จะให้กฎ ถ้าคุณจำแค่ว่า "ห้ามใช้วงกลม" คุณจะใช้ไม่เป็นในวันที่งานเปลี่ยน แต่ถ้าคุณรู้ว่ามุมแพ้เพราะไม่มีเส้นอ้างอิง คุณจะรู้เองว่าวันไหนมันไม่สำคัญ
บทถัดไปจะลงไปอีกชั้น — ทำไมสายตาถึงถอดค่าได้ไม่แม่นตั้งแต่แรก
ทำไมตาถึงไม่แม่น — กฎของเวเบอร์#
บันไดในบทที่แล้วบอกว่า ใครแม่นกว่าใคร แต่ยังไม่ได้บอกว่า ทำไมถึงไม่แม่นตั้งแต่แรก บทนี้ลงไปอีกชั้นเดียว แล้วคุณจะได้กลไกที่อธิบายทั้งบันได ทั้งเรื่องแกน และทั้งเรื่องที่จะเจอในบทที่ 9 พร้อมกัน
6.1 การทดลองที่ทำได้ในสามวินาที
ดูภาพนี้ แล้วตอบว่าคู่ไหนบอกความต่างได้ชัดกว่า
คำตอบชัดจนไม่ต้องคิด แต่สิ่งที่ควรสะดุดคือ ความต่างของทั้งสองคู่เท่ากันเป๊ะที่ 5 หน่วย ไม่มีการปัดเศษ ไม่มีการบิดสเกล ทั้งสองแผงใช้กฎเดียวกันในการแปลงค่าเป็นความสูง
6.2 ตาหารก่อนเสมอ
สิ่งที่เกิดขึ้นข้างบนมีชื่อ และถูกอธิบายไว้ตั้งแต่ศตวรรษที่ 19
💡 กฎของเวเบอร์ (Weber's law) — ความต่างที่น้อยที่สุดที่คนจะสังเกตเห็นได้ แปรตามขนาดของสิ่งที่กำลังดู ไม่ใช่ค่าคงที่ · เขียนเป็นสมการคือ ΔI / I = k โดย ΔI คือความต่างที่พอจะสังเกตออก I คือขนาดตั้งต้น และ k คือค่าคงที่ของช่องทางนั้น · แปลเป็นภาษาคนคือ ตาวัดเป็นอัตราส่วน ไม่ได้วัดเป็นผลต่าง
พอรู้ข้อนี้ ผลในภาพก็คำนวณล่วงหน้าได้ ไม่ต้องทดลอง — 5 หน่วยจาก 15 คือ 33% ส่วน 5 หน่วยจาก 1005 คือ 0.5% ตาได้รับตัวเลขสองตัวนี้ ไม่ได้รับเลข 5 ทั้งคู่
ตาไม่เคยถามว่า "ต่างกันเท่าไหร่" มันถามว่า "ต่างกันกี่ส่วนของของเดิม"
6.3 สามเรื่องที่กฎข้อเดียวนี้อธิบายได้ทันที
นี่คือสิ่งที่ทำให้กฎนี้คุ้มค่าที่จะจำ มันไม่ได้อธิบายแค่ภาพเดียว
หนึ่ง — ทำไมตัวเลขใหญ่ ๆ บนกราฟเดียวกันถึงดูเท่ากันไปหมด ยอดหลักล้านที่ต่างกันสองแสนดูต่างชัด แต่พอเป็นหลักร้อยล้านที่ต่างกันสองแสนเท่ากัน มันหายไปเลย ไม่ใช่เพราะกราฟทำไม่ดี แต่เพราะตัวหารโตขึ้น
สอง — ทำไมสเกล log ถึงไม่ใช่แค่ลูกเล่นคณิตศาสตร์ สเกล log ทำให้อัตราส่วนที่เท่ากันกินระยะบนแกนเท่ากัน ซึ่งบังเอิญตรงกับวิธีที่ตาอ่านพอดี มันจึงไม่ใช่การบิดข้อมูล แต่เป็นการจับสเกลของภาพให้ตรงกับสเกลของผู้อ่าน
สาม — ทำไมแท่งที่เริ่มจากฐานเดียวกันถึงสำคัญ เพราะฐานคือค่าที่ตาเอาไปหาร เปลี่ยนฐาน เปลี่ยนตัวหาร เปลี่ยนคำตอบ — ซึ่งพาเราไปหัวข้อถัดไป
6.4 อำนาจที่คนทำกราฟถืออยู่โดยไม่รู้ตัว
ถ้าตาหารด้วยค่าฐานเสมอ แล้วใครเป็นคนเลือกค่าฐาน
ข้อมูลคือ 102 กับ 100 ทั้งสองแผง ไม่ขยับแม้แต่หน่วยเดียว แต่พอเลื่อนจุดที่แกนเริ่มนับจาก 0 ไปเป็น 98 ความต่างที่ตาได้รับกระโดดจาก 1.96% เป็น 50.00%
ขยายขึ้น 25 เท่า โดยไม่แตะข้อมูลเลยสักตัว
⚠️ ตรงนี้ต้องระวัง เล่มนี้ยังไม่ได้บอกว่าตัดฐานแกนคือการโกหก บางครั้งมันคือสิ่งที่ถูกต้อง เช่นเวลาดูอุณหภูมิร่างกายที่ 36.5 กับ 38.5 องศา การเริ่มแกนที่ 0 คือการซ่อนเรื่องสำคัญ เส้นแบ่งระหว่าง "จำเป็น" กับ "หลอกตา" อยู่ที่บทที่ 9 ตอนนี้ขอแค่ให้เห็นว่า อำนาจนี้มีอยู่จริง และมันอยู่ในมือคุณทุกครั้งที่ทำกราฟ
6.5 ตรวจชุดข้อมูลของคุณเอง
สคริปต์นี้ (weber.py) รวมทั้งสามเรื่องในบท และท้ายสุดพิมพ์ความสูงแท่งที่ใช้วาด FIG 6.1 กับ 6.2 ให้ตรวจได้ว่าภาพตรงกับเลข · ให้ลองเปลี่ยนค่า K แล้วดูว่าคำตัดสินขยับยังไง
"""กฎของเวเบอร์ — ตาอ่าน 'กี่เท่า' ไม่ใช่ 'ต่างกันเท่าไหร่'"""
K = 0.05 # ความต่างต้องเป็นอย่างน้อย 5% ของค่าฐาน ถึงจะหวังให้ตาแยกออก
def detectable(a, b, k=K):
"""ต่างกันพอให้ตาแยกออกไหม — เทียบเป็นสัดส่วนของค่าที่ใหญ่กว่า"""
return abs(a - b) / max(a, b) >= k
print("=== ความต่างเท่ากันเป๊ะ 5 หน่วย ที่ระดับค่าต่างกัน ===")
for base in (10, 50, 100, 500, 1000):
a, b = base, base + 5
ratio = 5 / b
print(f" {a:>5} กับ {b:>5} -> ต่างกัน {ratio * 100:5.2f}% "
f"-> {'แยกออก' if detectable(a, b) else 'แยกไม่ออก'}")
print("\n=== อัตราส่วนเท่ากัน ที่ระดับค่าต่างกัน ===")
for base in (10, 100, 1000):
b = base * 1.10
print(f" {base:>5} กับ {b:>7.1f} -> ต่างกัน 10.00% "
f"-> {'แยกออก' if detectable(base, b) else 'แยกไม่ออก'}")
print(" ระดับค่าเปลี่ยนไป 100 เท่า แต่ผลเหมือนกันหมด นั่นคือสิ่งที่กฎนี้พูด")
print("\n=== ตัดฐานแกนออก แล้วอัตราส่วนที่ตาเห็นเปลี่ยนไปแค่ไหน ===")
A, B = 102, 100
for floor in (0, 90, 98, 99):
ha, hb = A - floor, B - floor
ratio = (ha - hb) / ha
print(f" แกนเริ่มที่ {floor:>3} -> แท่งสูง {ha:>4} กับ {hb:>4} "
f"-> ตาเห็นต่างกัน {ratio * 100:6.2f}% "
f"-> {'แยกออก' if ratio >= K else 'แยกไม่ออก'}")
print(f" ข้อมูลไม่ขยับเลย {A} กับ {B} ต่างกัน 2 หน่วยตลอด")
print(" สิ่งที่ขยับคือค่าฐานที่ตาเอาไปหาร ซึ่งคนทำกราฟเป็นคนเลือก")
# --- ความสูงแท่งที่ใช้วาดสองภาพในบทนี้ เผื่ออยากตรวจว่าภาพตรงกับเลข ---
H = 120.0
print("\n=== ความสูงแท่งของ FIG 6.1 ===")
for pair in ((10, 15), (1000, 1005)):
hi = max(pair)
hs = [round(v / hi * H, 2) for v in pair]
print(f" {pair} -> สูง {hs} px -> ห่างกัน {round(hs[1] - hs[0], 2)} px")
print("\n=== ความสูงแท่งของ FIG 6.2 ===")
for floor in (0, 98):
ha, hb = A - floor, B - floor
low = round(hb / ha * H, 2)
print(f" ฐานแกน {floor:>3} -> สูง {low} กับ {H} px -> ห่างกัน {round(H - low, 2)} px")ผลรันจริง
=== ความต่างเท่ากันเป๊ะ 5 หน่วย ที่ระดับค่าต่างกัน ===
10 กับ 15 -> ต่างกัน 33.33% -> แยกออก
50 กับ 55 -> ต่างกัน 9.09% -> แยกออก
100 กับ 105 -> ต่างกัน 4.76% -> แยกไม่ออก
500 กับ 505 -> ต่างกัน 0.99% -> แยกไม่ออก
1000 กับ 1005 -> ต่างกัน 0.50% -> แยกไม่ออก
=== อัตราส่วนเท่ากัน ที่ระดับค่าต่างกัน ===
10 กับ 11.0 -> ต่างกัน 10.00% -> แยกออก
100 กับ 110.0 -> ต่างกัน 10.00% -> แยกออก
1000 กับ 1100.0 -> ต่างกัน 10.00% -> แยกออก
ระดับค่าเปลี่ยนไป 100 เท่า แต่ผลเหมือนกันหมด นั่นคือสิ่งที่กฎนี้พูด
=== ตัดฐานแกนออก แล้วอัตราส่วนที่ตาเห็นเปลี่ยนไปแค่ไหน ===
แกนเริ่มที่ 0 -> แท่งสูง 102 กับ 100 -> ตาเห็นต่างกัน 1.96% -> แยกไม่ออก
แกนเริ่มที่ 90 -> แท่งสูง 12 กับ 10 -> ตาเห็นต่างกัน 16.67% -> แยกออก
แกนเริ่มที่ 98 -> แท่งสูง 4 กับ 2 -> ตาเห็นต่างกัน 50.00% -> แยกออก
แกนเริ่มที่ 99 -> แท่งสูง 3 กับ 1 -> ตาเห็นต่างกัน 66.67% -> แยกออก
ข้อมูลไม่ขยับเลย 102 กับ 100 ต่างกัน 2 หน่วยตลอด
สิ่งที่ขยับคือค่าฐานที่ตาเอาไปหาร ซึ่งคนทำกราฟเป็นคนเลือก
=== ความสูงแท่งของ FIG 6.1 ===
(10, 15) -> สูง [80.0, 120.0] px -> ห่างกัน 40.0 px
(1000, 1005) -> สูง [119.4, 120.0] px -> ห่างกัน 0.6 px
=== ความสูงแท่งของ FIG 6.2 ===
ฐานแกน 0 -> สูง 117.65 กับ 120.0 px -> ห่างกัน 2.35 px
ฐานแกน 98 -> สูง 60.0 กับ 120.0 px -> ห่างกัน 60.0 pxค่า K = 0.05 ในสคริปต์เป็นเกณฑ์อนุรักษ์นิยมที่ตั้งไว้ให้ลองเล่น ไม่ใช่ค่าคงที่ของธรรมชาติ ค่าจริงขึ้นกับช่องทาง ขนาดภาพ ระยะมอง และตัวคนดู สิ่งที่ไม่เปลี่ยนคือรูปแบบ — เป็นอัตราส่วน ไม่ใช่ผลต่าง
หมึกที่ไม่ได้บอกอะไร#
บทนี้ไขปริศนาข้อที่สองที่ปลูกไว้ตั้งแต่บทที่ 1 — ทำไมสไลด์ที่สวยกว่า กลับสื่อสารได้แย่กว่า
คำตอบไม่ใช่ "เพราะความเรียบง่ายดูดีกว่า" ซึ่งเป็นเรื่องรสนิยม แต่เป็นเรื่องกลไกที่คุณได้มาแล้วตั้งแต่บทที่ 2
7.1 ของตกแต่งไม่ได้อยู่เฉย ๆ มันแย่งช่องทาง
บทที่ 2 บอกว่าสายตามีช่องทางแบบขนานอยู่ไม่กี่ช่อง — สี ขนาด ความเข้ม ทิศทาง และตำแหน่งที่หลุดจากแนว
เงาใต้แท่งใช้ ความเข้ม · เส้นตารางใช้ ตำแหน่ง · กรอบใช้ ทิศทาง · คำอธิบายสัญลักษณ์ใช้ สี
ทุกชิ้นที่คุณเติมเข้าไปเพื่อความสวย จึงไม่ได้แค่ "อยู่เฉย ๆ" มันเข้าไปยืนในช่องทางเดียวกับที่ข้อมูลต้องใช้ และช่องทางพวกนั้นมีจำกัด
ของตกแต่งไม่ได้เป็นกลาง มันเป็นคู่แข่งของข้อมูล บนช่องทางเดียวกัน
7.2 นับหมึกกันตรง ๆ
💡 data-ink ratio — สัดส่วนของหมึกบนหน้าที่แบกข้อมูลจริง เทียบกับหมึกทั้งหมด · แนวคิดนี้มาจาก Edward Tufte ซึ่งเสนอว่ากราฟที่ดีควรดันสัดส่วนนี้ให้สูงที่สุดเท่าที่ยังอ่านรู้เรื่อง · เล่มนี้นับเป็น จำนวนชิ้น แทนปริมาณหมึกจริง เพราะชิ้นคือหน่วยที่สายตาต้องประมวลผล และนับตรวจซ้ำได้
สคริปต์นี้ (dataink.py) นับชิ้นในภาพข้างบนทีละชิ้น ตามที่ตาเห็นจริง
"""นับหมึกในกราฟ — ชิ้นไหนแบกข้อมูล ชิ้นไหนแค่มาอยู่เฉย ๆ"""
# นับจากภาพ FIG 7.1 ในบทนี้ ทีละชิ้นตามที่ตาเห็น
BEFORE = [
("แท่ง 5 อัน", 5, True),
("เส้นตารางแนวนอน", 6, False),
("กรอบรอบกราฟ 4 ด้าน", 4, False),
("เงาใต้แท่ง 5 อัน", 5, False),
("คำอธิบายสัญลักษณ์", 5, False),
("แกนตั้งพร้อมขีด 6 ขีด", 7, False),
("แกนนอน", 1, False),
]
AFTER = [
("แท่ง 5 อัน", 5, True),
("ตัวเลขบนแท่ง", 5, True),
("ป้ายชื่อใต้แท่ง", 5, True),
("เส้นฐานเส้นเดียว", 1, False),
]
def report(name, items):
data = sum(n for _, n, is_data in items if is_data)
total = sum(n for _, n, _ in items)
print(f"=== {name} ===")
for label, n, is_data in items:
tag = "แบกข้อมูล" if is_data else "ไม่แบกข้อมูล"
print(f" {tag:<13}{label:<26} {n:>3}")
print(f" รวม {total} ชิ้น · แบกข้อมูล {data} ชิ้น "
f"· สัดส่วนหมึกที่เป็นข้อมูล {data / total * 100:.1f}%\n")
return data, total
d1, t1 = report("ก่อนแก้", BEFORE)
d2, t2 = report("หลังแก้", AFTER)
print(f"ชิ้นที่ตาต้องประมวลผลลดจาก {t1} เหลือ {t2} ({(1 - t2 / t1) * 100:.0f}% หายไป)")
print(f"แต่ข้อมูลที่สื่อได้เพิ่มจาก {d1} เป็น {d2} ชิ้น")
print("นี่คือจุดที่คำว่า 'เรียบง่ายกว่า' ไม่ได้แปลว่า 'บอกน้อยกว่า'")ผลรันจริง
=== ก่อนแก้ ===
แบกข้อมูล แท่ง 5 อัน 5
ไม่แบกข้อมูล เส้นตารางแนวนอน 6
ไม่แบกข้อมูล กรอบรอบกราฟ 4 ด้าน 4
ไม่แบกข้อมูล เงาใต้แท่ง 5 อัน 5
ไม่แบกข้อมูล คำอธิบายสัญลักษณ์ 5
ไม่แบกข้อมูล แกนตั้งพร้อมขีด 6 ขีด 7
ไม่แบกข้อมูล แกนนอน 1
รวม 33 ชิ้น · แบกข้อมูล 5 ชิ้น · สัดส่วนหมึกที่เป็นข้อมูล 15.2%
=== หลังแก้ ===
แบกข้อมูล แท่ง 5 อัน 5
แบกข้อมูล ตัวเลขบนแท่ง 5
แบกข้อมูล ป้ายชื่อใต้แท่ง 5
ไม่แบกข้อมูล เส้นฐานเส้นเดียว 1
รวม 16 ชิ้น · แบกข้อมูล 15 ชิ้น · สัดส่วนหมึกที่เป็นข้อมูล 93.8%
ชิ้นที่ตาต้องประมวลผลลดจาก 33 เหลือ 16 (52% หายไป)
แต่ข้อมูลที่สื่อได้เพิ่มจาก 5 เป็น 15 ชิ้น
นี่คือจุดที่คำว่า 'เรียบง่ายกว่า' ไม่ได้แปลว่า 'บอกน้อยกว่า'สังเกตบรรทัดสุดท้ายให้ดี ชิ้นลดลงครึ่งหนึ่ง แต่ข้อมูลที่สื่อได้เพิ่มขึ้นสามเท่า เพราะตัวเลขบนแท่งกับป้ายชื่อใต้แท่งนับเป็นข้อมูล ไม่ใช่การตกแต่ง
⚠️ ป้ายในบัญชีคือ "ไม่แบกข้อมูล" ไม่ใช่ "ตกแต่ง" และสองคำนี้ไม่เหมือนกัน — แกนกับขีดบอกสเกลถูกนับเป็นไม่แบกข้อมูล แต่มันเป็นหมึกที่ชอบธรรม ไม่ใช่ขยะ มันหมดความจำเป็นเฉพาะในกรณีนี้ เพราะพอพิมพ์ค่าติดไว้บนแท่งทุกอันแล้ว แกนก็ไม่เหลืองานให้ทำ · ถ้าคุณไม่ได้พิมพ์ค่า แกนคือชิ้นแรกที่ห้ามลบ
7.3 เส้นแบ่งที่ Tufte ไม่ได้ขีดไว้
กฎ "ลบให้มากที่สุด" มีจุดที่มันพัง และเล่มนี้ต้องบอกให้ชัดเพราะกฎนี้ถูกอ้างแบบสุดโต่งบ่อยมาก
สิ่งที่ ลบได้เกือบเสมอ คือชิ้นที่ไม่ได้ตอบคำถามอะไรเลย — เงา ไล่สี กรอบ เส้นตารางที่ถี่เกินจำเป็น ลวดลายพื้นหลัง
สิ่งที่ ห้ามลบ แม้จะดูเหมือนของตกแต่ง คือชิ้นที่ทำหน้าที่บอกบริบท — หน่วยของตัวเลข ช่วงเวลา ที่มาของข้อมูล และเส้นอ้างอิงอย่างค่าเป้าหมายหรือค่าเฉลี่ย พวกนี้กินหมึก แต่มันแบกความหมาย
เส้นแบ่งจริงไม่ใช่ "สวยหรือไม่สวย" แต่คือคำถามเดียว — ถ้าลบชิ้นนี้ออก มีคำถามไหนที่คนอ่านตอบไม่ได้อีกต่อไปหรือเปล่า ถ้าไม่มี ลบได้
LEVEL 2 · ระดับกลาง
สี — ช่องทางที่ผิดบ่อยที่สุด#
สีอยู่ขั้นล่างสุดของบันไดในบทที่ 5 แต่กลับเป็นช่องทางที่คนหยิบมาใช้บ่อยที่สุด บทนี้อธิบายว่าทำไมมันถึงพัง และพังตรงไหนกันแน่
8.1 สีทำงานสองแบบ และคนมักสลับกัน
มีคำถามเดียวที่ต้องตอบให้ได้ก่อนแตะเครื่องมือเลือกสี — สีกำลังบอกว่า "คนละพวก" หรือบอกว่า "มากกว่า-น้อยกว่า"
| ใช้สีเพื่อ | ต้องการชุดสีแบบ | หลักการ |
|---|---|---|
| แยกกลุ่มที่ไม่มีลำดับ | หลายสีที่ต่างชนิดกัน | ความสว่างควรใกล้กัน ไม่งั้นจะดูเหมือนมีลำดับทั้งที่ไม่มี |
| บอกปริมาณจากน้อยไปมาก | สีเดียว ไล่ความสว่าง | ความสว่างต้องไล่ทางเดียว ห้ามกลับทิศ |
| บอกระยะห่างจากจุดกลาง | สองสี พบกันตรงกลางที่สว่างที่สุด | จุดกลางต้องมีความหมายจริง เช่น ศูนย์ หรือค่าเป้าหมาย |
ความผิดพลาดที่พบบ่อยที่สุดคือเอาชุดแบบแรกไปทำงานของแบบที่สอง — ใช้หลายสีเพื่อบอกปริมาณ ซึ่งพาเราไปหาชุดสีรุ้ง
8.2 ทำไมชุดสีรุ้งถึงพัง
เส้นข้างล่างชุดสีคือ ความสว่างสัมพัทธ์ ของแต่ละขั้น ไม่ใช่ของตกแต่ง
ชุดสีรุ้งกลับทิศ 3 ครั้ง — ความสว่างขึ้นแล้วลง ลงแล้วขึ้น ตาที่พยายามเรียงลำดับจากภาพจึงหาลำดับไม่เจอ และยังไปสร้าง ขอบเขตปลอม ตรงจุดที่ความสว่างกระโดด ทำให้ข้อมูลที่ค่อย ๆ เปลี่ยนดูเหมือนมีเส้นแบ่งชัด ๆ ทั้งที่ไม่มี
ส่วนชุดไล่เฉดสีเดียวลดลงทางเดียวตลอด ไม่กลับทิศเลยสักครั้ง ตาจึงเรียงลำดับได้โดยไม่ต้องดูคำอธิบายสัญลักษณ์
💡 ทำไมความสว่างถึงเป็นตัวชี้ขาด — คนที่มีภาวะตาบอดสีจะแยก เฉดสี บางคู่ไม่ออก แต่ยังเห็น ความสว่าง ได้ปกติ · ชุดสีที่ไล่ความสว่างทางเดียว จึงยังอ่านรู้เรื่องทั้งกับคนตาบอดสี ทั้งเวลาพิมพ์ขาวดำ และทั้งเวลาโปรเจกเตอร์สีเพี้ยน · ตัวเลขที่ถูกอ้างบ่อยที่สุดคือ 8% ของผู้ชาย แต่นั่นเป็นค่าของประชากรเชื้อสายยุโรปเหนือ ส่วนประชากรเอเชียตะวันออกและเอเชียตะวันออกเฉียงใต้ต่ำกว่านั้น ราว 4 ถึง 5% · ในห้องประชุมที่มีผู้ชาย 12 คน ช่วงนี้แปลเป็นโอกาสมีอย่างน้อยหนึ่งคนราว 43% ถึง 63% ขึ้นกับว่าใช้อัตราไหน — ไม่ใช่เรื่องแน่นอน แต่ก็บ่อยพอที่จะไม่ควรเสี่ยง
8.3 วัดชุดสีของคุณเอง
ชุดสีทั้งสองในภาพไม่ได้เลือกด้วยมือ มันถูกสร้างและวัดด้วยสคริปต์นี้ (color.py) · ค่า hex ที่มันพิมพ์ออกมาคือค่าที่เว็บนี้ใช้วาดแถบสีข้างบนจริง ๆ
"""สร้างชุดสีสองแบบแล้ววัดความสว่างของแต่ละขั้น — Python มาตรฐานล้วน"""
import colorsys
N = 7
def to_hex(r, g, b):
return "#%02x%02x%02x" % (round(r * 255), round(g * 255), round(b * 255))
def luminance(r, g, b):
"""ความสว่างสัมพัทธ์ตามสูตร WCAG — ถ่วงน้ำหนักตามความไวของตาต่อแต่ละช่องสี"""
def lin(c):
return c / 12.92 if c <= 0.03928 else ((c + 0.055) / 1.055) ** 2.4
return 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b)
# แบบที่หนึ่ง: กวาดสีรุ้ง จากน้ำเงินไปแดง ความสว่างปล่อยตามยถากรรม
rainbow = [colorsys.hls_to_rgb(h, 0.5, 0.95)
for h in [(240 - 240 * i / (N - 1)) / 360 for i in range(N)]]
# แบบที่สอง: สีเดียว ไล่ความสว่างอย่างเดียว
seq = [colorsys.hls_to_rgb(20 / 360, 0.92 - 0.72 * i / (N - 1), 0.55) for i in range(N)]
for name, ramp in (("สีรุ้ง", rainbow), ("ไล่เฉดสีเดียว", seq)):
print(f"=== {name} ===")
lums = []
for i, (r, g, b) in enumerate(ramp):
L = luminance(r, g, b)
lums.append(L)
print(f" ขั้น {i + 1}: {to_hex(r, g, b)} ความสว่าง {L:.3f}")
steps = [round(lums[i + 1] - lums[i], 3) for i in range(len(lums) - 1)]
print(f" ผลต่างความสว่างทีละขั้น: {steps}")
mono = all(s < 0 for s in steps) or all(s > 0 for s in steps)
print(f" ไล่ทางเดียวตลอดไหม: {'ใช่' if mono else 'ไม่ใช่'}")
flips = sum(1 for i in range(len(steps) - 1) if steps[i] * steps[i + 1] < 0)
print(f" จำนวนครั้งที่กลับทิศ: {flips}\n")ผลรันจริง
=== สีรุ้ง ===
ขั้น 1: #0606f9 ความสว่าง 0.070
ขั้น 2: #06a8f9 ความสว่าง 0.348
ขั้น 3: #06f9a8 ความสว่าง 0.704
ขั้น 4: #06f906 ความสว่าง 0.676
ขั้น 5: #a8f906 ความสว่าง 0.758
ขั้น 6: #f9a806 ความสว่าง 0.480
ขั้น 7: #f90606 ความสว่าง 0.202
ผลต่างความสว่างทีละขั้น: [0.278, 0.356, -0.028, 0.083, -0.278, -0.278]
ไล่ทางเดียวตลอดไหม: ไม่ใช่
จำนวนครั้งที่กลับทิศ: 3
=== ไล่เฉดสีเดียว ===
ขั้น 1: #f6e7df ความสว่าง 0.820
ขั้น 2: #e8c3b0 ความสว่าง 0.592
ขั้น 3: #da9e81 ความสว่าง 0.411
ขั้น 4: #cd7a51 ความสว่าง 0.275
ขั้น 5: #ae5c32 ความสว่าง 0.168
ขั้น 6: #7e4325 ความสว่าง 0.086
ขั้น 7: #4f2a17 ความสว่าง 0.034
ผลต่างความสว่างทีละขั้น: [-0.228, -0.18, -0.136, -0.107, -0.082, -0.052]
ไล่ทางเดียวตลอดไหม: ใช่
จำนวนครั้งที่กลับทิศ: 0เอาชุดสีที่คุณใช้อยู่มาใส่แล้วรันดู ถ้า จำนวนครั้งที่กลับทิศไม่เป็นศูนย์ และคุณกำลังใช้มันบอกปริมาณ คุณเจอปัญหาแล้ว
⚠️ สคริปต์นี้ตรวจได้อย่างเดียวคือความสว่างไล่ทางเดียวไหม มันไม่ได้บอกว่าชุดสี ห่างกันสม่ำเสมอ ในการรับรู้หรือเปล่า ซึ่งเป็นคนละเรื่องและต้องใช้ปริภูมิสีที่ออกแบบมาเพื่อการรับรู้โดยเฉพาะ
8.4 สามข้อที่ใช้ได้พรุ่งนี้
- นับสีก่อน ถ้าต้องใช้เกินหกเจ็ดสีเพื่อแยกกลุ่ม แปลว่าไม่ควรแยกด้วยสีตั้งแต่แรก ให้จัดกลุ่มใหม่หรือแยกเป็นภาพชุด
- เน้นด้วยสีเดียว บทที่ 2 บอกแล้วว่าสีเด้งแรงที่สุดเมื่อมีสีเดียวที่ต่างจากพวก · ยิ่งเติมสีที่ต้องแยกจากกันมากขึ้น การอ่านยิ่งเลื่อนไปทางไล่หาทีละชิ้น
- ทดสอบด้วยการทำให้เป็นขาวดำ ถ้าภาพยังอ่านรู้เรื่อง แปลว่าคุณไม่ได้ให้สีแบกงานที่มันแบกไม่ไหว
แกนโกหกได้#
บทที่ 6 ทิ้งอำนาจข้อหนึ่งไว้ให้คุณโดยยังไม่ตัดสิน — คนทำกราฟเป็นคนเลือกค่าฐานที่ตาจะเอาไปหาร บทนี้คือบทที่เราเอาอำนาจนั้นมาวัดเป็นตัวเลข แล้วขีดเส้นว่าตรงไหนคือการเน้น ตรงไหนคือการหลอก
9.1 วัดคำโกหกเป็นตัวเลข
💡 ตัวคูณคำโกหก (lie factor) — ขนาดของผลที่ ภาพ แสดง หารด้วยขนาดของผลที่ ข้อมูล มีจริง · แนวคิดของ Edward Tufte · ถ้าเท่ากับ 1 แปลว่าภาพซื่อสัตย์ตามสัดส่วน ยิ่งห่างจาก 1 ภาพยิ่งบิดเบี้ยว
ข้อมูลชุดเดียวกันทั้งสามแผง 102 กับ 100 ต่างกัน 2% เท่าเดิมตลอด แต่ตัวคูณวิ่งจาก 1.00 ไป 100.00
"""ตัวคูณคำโกหก — ขนาดของผลที่ภาพแสดง หารด้วยขนาดของผลที่มีจริงในข้อมูล"""
def lie_factor(a, b, floor):
"""a, b = ค่าจริงสองค่า · floor = จุดที่แกนเริ่มนับ"""
real = abs(a - b) / min(a, b) # ผลจริง คิดเป็นสัดส่วน
ha, hb = a - floor, b - floor
shown = abs(ha - hb) / min(ha, hb) # ผลที่ตาเห็นจากความยาวแท่ง
return real, shown, shown / real
print(f"{'ฐานแกน':>8}{'ผลจริง':>12}{'ผลที่ภาพแสดง':>16}{'ตัวคูณคำโกหก':>16} คำตัดสิน")
for floor in (0, 90, 95, 98, 99):
real, shown, lf = lie_factor(102, 100, floor)
verdict = "ซื่อสัตย์" if 0.95 <= lf <= 1.05 else f"เกินจริง {lf:.0f} เท่า"
print(f"{floor:>8}{real * 100:>11.2f}%{shown * 100:>15.2f}%{lf:>16.2f} {verdict}")
print("\nTufte เสนอว่าตัวคูณควรอยู่ราว 1 — ห่างจาก 1 มากเท่าไหร่ ภาพก็โกหกมากเท่านั้น")
print("แต่ 'โกหก' ที่นี่หมายถึงสัดส่วนบิดเบี้ยว ไม่ได้แปลว่าคนทำตั้งใจหลอก")
# --- ความสูงแท่งที่ใช้วาด FIG 9.1 ในบทนี้ ---
H = 110.0
print("\n=== ความสูงแท่งของ FIG 9.1 ===")
for floor in (0, 95, 99):
ha, hb = 102 - floor, 100 - floor
low = round(hb / ha * H, 2)
_, _, lf = lie_factor(102, 100, floor)
print(f" ฐานแกน {floor:>3} -> สูง {low:>6} กับ {H} px -> ตัวคูณ {lf:.2f}")ผลรันจริง
ฐานแกน ผลจริง ผลที่ภาพแสดง ตัวคูณคำโกหก คำตัดสิน
0 2.00% 2.00% 1.00 ซื่อสัตย์
90 2.00% 20.00% 10.00 เกินจริง 10 เท่า
95 2.00% 40.00% 20.00 เกินจริง 20 เท่า
98 2.00% 100.00% 50.00 เกินจริง 50 เท่า
99 2.00% 200.00% 100.00 เกินจริง 100 เท่า
Tufte เสนอว่าตัวคูณควรอยู่ราว 1 — ห่างจาก 1 มากเท่าไหร่ ภาพก็โกหกมากเท่านั้น
แต่ 'โกหก' ที่นี่หมายถึงสัดส่วนบิดเบี้ยว ไม่ได้แปลว่าคนทำตั้งใจหลอก
=== ความสูงแท่งของ FIG 9.1 ===
ฐานแกน 0 -> สูง 107.84 กับ 110.0 px -> ตัวคูณ 1.00
ฐานแกน 95 -> สูง 78.57 กับ 110.0 px -> ตัวคูณ 20.00
ฐานแกน 99 -> สูง 36.67 กับ 110.0 px -> ตัวคูณ 100.009.2 แต่บางครั้งการเริ่มที่ศูนย์ต่างหากที่โกหก
ถ้าจบบทตรงนี้ คุณจะได้กฎ "แท่งต้องเริ่มจากศูนย์เสมอ" กลับไป ซึ่งผิดพอ ๆ กับที่มันถูก
ลองนึกถึงอุณหภูมิร่างกาย 36.5 กับ 38.5 องศา ถ้าบังคับให้แกนเริ่มที่ 0 แท่งสองอันจะสูงเกือบเท่ากันและภาพจะบอกว่า "ไม่มีอะไร" ทั้งที่คนหนึ่งไข้ขึ้น นั่นคือการซ่อนเรื่องสำคัญ ซึ่งก็เป็นการโกหกอีกแบบหนึ่ง
ความต่างอยู่ตรงนี้ — ศูนย์เป็นจุดอ้างอิงที่มีความหมายจริงหรือเปล่า
| ศูนย์มีความหมาย | ศูนย์ไม่มีความหมาย |
|---|---|
| ยอดขาย · จำนวนคน · งบที่ใช้ไป | อุณหภูมิร่างกาย · ปีปฏิทิน · คะแนนสอบที่เริ่มนับจาก 200 |
| "ศูนย์" แปลว่าไม่มีเลย | "ศูนย์" ไม่เคยเกิดขึ้นในความเป็นจริง |
| แท่งต้องเริ่มที่ศูนย์ | เริ่มที่ศูนย์คือการเสียพื้นที่ไปกับช่วงที่ไม่มีข้อมูล |
9.3 กฎที่ใช้ได้จริงมีสองข้อ ไม่ใช่ข้อเดียว
ข้อแรก ความยาวต้องเริ่มจากศูนย์ ตำแหน่งไม่ต้อง เพราะกราฟแท่งเข้ารหัสด้วย ความยาว ซึ่งอ่านค่าจากจุดเริ่มถึงจุดจบ ตัดฐานทิ้งเท่ากับโกหกเรื่องความยาว ส่วนกราฟเส้นหรือจุดเข้ารหัสด้วย ตำแหน่ง ซึ่งอ่านจากการเทียบกับแกน ไม่ได้อ่านจากความยาว การซูมแกนจึงไม่ผิดหลักการ
นี่คือเหตุผลที่กราฟเส้นซูมได้แต่กราฟแท่งซูมไม่ได้ และมันไม่ใช่ธรรมเนียม แต่มาจากบันไดในบทที่ 5 โดยตรง
ข้อสอง ถ้าซูม ต้องบอกให้เห็น ทำเครื่องหมายว่าแกนถูกตัด เขียนช่วงของแกนให้ชัด และอย่าให้คนอ่านต้องไปสังเกตเอาเองจากตัวเลขเล็ก ๆ ที่มุมภาพ
⚠️ ข้อนี้สำคัญกว่าที่คิด เพราะงานส่วนใหญ่ที่ถูกกล่าวหาว่า "จงใจหลอก" จริง ๆ แล้วมาจากค่าเริ่มต้นของเครื่องมือที่ซูมแกนให้อัตโนมัติ โดยคนทำไม่ได้ตั้งใจและไม่รู้ตัวด้วยซ้ำ การรู้กลไกจึงป้องกันได้ทั้งการโดนหลอกและการเผลอหลอกคนอื่น
ถามว่าศูนย์มีความหมายไหม ก่อนถามว่าแกนควรเริ่มที่ไหน
เมื่อค่าสรุปปิดบังความจริง#
มาถึงตรงนี้ทั้งเล่มพูดเรื่องเดียวกันมาตลอด คือความสูญเสียตอนตาถอดรหัส ซึ่งอาจทำให้คุณคิดว่า ถ้าอย่างนั้นอ่านจากตัวเลขก็สิ้นเรื่อง ไม่ต้องวาดเลย
บทนี้คือบทที่หักล้างความคิดนั้น และมันคือคำตอบของปริศนาข้อที่สามที่ปลูกไว้ตั้งแต่บทที่ 1
10.1 สี่ชุดข้อมูลที่ค่าสรุปเหมือนกันหมด
ทั้งสี่ชุดมีค่าเฉลี่ยของ x เท่ากัน ค่าเฉลี่ยของ y เท่ากัน ความแปรปรวนเท่ากัน สหสัมพันธ์เท่ากัน และเส้นถดถอยเป็นเส้นเดียวกันเป๊ะ
ถ้าคุณได้รับแค่ตารางค่าสรุป คุณจะรายงานว่าทั้งสี่ชุดเป็นเรื่องเดียวกัน แต่พอวาดออกมา
- ชุด I กระจายรอบเส้นตามปกติ เป็นชุดเดียวที่เส้นถดถอยอธิบายได้จริง
- ชุด II โค้งชัดเจน การลากเส้นตรงผ่านมันคือการอธิบายผิดชนิด
- ชุด III เรียงตรงเป๊ะ ยกเว้นจุดเดียวที่ดึงเส้นให้เอียงไปจากความจริง
- ชุด IV x เท่ากันหมดยกเว้นจุดเดียว ซึ่งแปลว่า จุดเดียวนั้นลากเส้นทั้งเส้นไปเอง ลบมันออก แล้วความชันจะไม่มีความหมายอะไรเลย
ชุด IV คือชุดที่น่ากลัวที่สุดในงานจริง เพราะค่าสรุปดูดีทุกตัว แต่ข้อสรุปทั้งหมดแขวนอยู่บนข้อมูลจุดเดียว
10.2 พิสูจน์ด้วยตัวเอง
ชุดนี้เรียกว่า Anscombe's quartet เผยแพร่โดย Francis Anscombe ปี 1973 · สคริปต์ข้างล่าง (anscombe.py) คำนวณค่าสรุปทั้งห้าตัวจากข้อมูลดิบ ไม่ได้ลอกตัวเลขมา แล้วพิมพ์พิกัดทุกจุดที่ใช้วาด FIG 10.1 ต่อท้าย
"""ชุด Anscombe — ค่าสรุปเท่ากันเกือบทุกตัว แต่ภาพคนละเรื่อง"""
X123 = [10, 8, 13, 9, 11, 14, 6, 4, 12, 7, 5]
SETS = {
"I": (X123, [8.04, 6.95, 7.58, 8.81, 8.33, 9.96, 7.24, 4.26, 10.84, 4.82, 5.68]),
"II": (X123, [9.14, 8.14, 8.74, 8.77, 9.26, 8.10, 6.13, 3.10, 9.13, 7.26, 4.74]),
"III": (X123, [7.46, 6.77, 12.74, 7.11, 7.81, 8.84, 6.08, 5.39, 8.15, 6.42, 5.73]),
"IV": ([8, 8, 8, 8, 8, 8, 8, 19, 8, 8, 8],
[6.58, 5.76, 7.71, 8.84, 8.47, 7.04, 5.25, 12.50, 5.56, 7.91, 6.89]),
}
def mean(v):
return sum(v) / len(v)
def var(v): # ความแปรปรวนแบบกลุ่มตัวอย่าง (หารด้วย n-1)
m = mean(v)
return sum((x - m) ** 2 for x in v) / (len(v) - 1)
def corr(x, y):
mx, my = mean(x), mean(y)
sxy = sum((a - mx) * (b - my) for a, b in zip(x, y))
sxx = sum((a - mx) ** 2 for a in x)
syy = sum((b - my) ** 2 for b in y)
return sxy / (sxx * syy) ** 0.5
def fit(x, y): # เส้นถดถอยด้วยวิธีกำลังสองน้อยสุด
mx, my = mean(x), mean(y)
slope = sum((a - mx) * (b - my) for a, b in zip(x, y)) / sum((a - mx) ** 2 for a in x)
return my - slope * mx, slope
print(f"{'ชุด':<5}{'x เฉลี่ย':>9}{'y เฉลี่ย':>10}{'var x':>8}{'var y':>8}"
f"{'สหสัมพันธ์':>12} เส้นถดถอย")
for name, (x, y) in SETS.items():
b0, b1 = fit(x, y)
print(f"{name:<5}{mean(x):>9.2f}{mean(y):>10.2f}{var(x):>8.2f}{var(y):>8.2f}"
f"{corr(x, y):>12.3f} y = {b0:.2f} + {b1:.2f}x")
print(f"\nค่าสรุปตรงกันถึงทศนิยมตำแหน่งที่สอง (คลาดกันในหลักสุดท้ายบ้าง) แต่ลองวาดดูสิ")
# --- พิกัดที่ใช้วาด FIG 10.1: x 4-19 -> 0-190 px, y 3-13 -> 150-0 px ---
print("\n=== พิกัดจุดของ FIG 10.1 (px) ===")
for name, (x, y) in SETS.items():
pts = [(round((a - 4) / 15 * 190, 1), round(150 - (b - 3) / 10 * 150, 1))
for a, b in zip(x, y)]
print(f" {name}: {pts}")
b0, b1 = fit(*SETS["I"])
print(f"\nเส้นถดถอยร่วม y = {b0:.2f} + {b1:.2f}x")
for xv in (4, 19):
yv = b0 + b1 * xv
print(f" x={xv} -> px ({round((xv - 4) / 15 * 190, 1)}, "
f"{round(150 - (yv - 3) / 10 * 150, 1)})")ผลรันจริง
ชุด x เฉลี่ย y เฉลี่ย var x var y สหสัมพันธ์ เส้นถดถอย
I 9.00 7.50 11.00 4.13 0.816 y = 3.00 + 0.50x
II 9.00 7.50 11.00 4.13 0.816 y = 3.00 + 0.50x
III 9.00 7.50 11.00 4.12 0.816 y = 3.00 + 0.50x
IV 9.00 7.50 11.00 4.12 0.817 y = 3.00 + 0.50x
ค่าสรุปตรงกันถึงทศนิยมตำแหน่งที่สอง (คลาดกันในหลักสุดท้ายบ้าง) แต่ลองวาดดูสิ
=== พิกัดจุดของ FIG 10.1 (px) ===
I: [(76.0, 74.4), (50.7, 90.8), (114.0, 81.3), (63.3, 62.8), (88.7, 70.0), (126.7, 45.6), (25.3, 86.4), (0.0, 131.1), (101.3, 32.4), (38.0, 122.7), (12.7, 109.8)]
II: [(76.0, 57.9), (50.7, 72.9), (114.0, 63.9), (63.3, 63.5), (88.7, 56.1), (126.7, 73.5), (25.3, 103.0), (0.0, 148.5), (101.3, 58.0), (38.0, 86.1), (12.7, 123.9)]
III: [(76.0, 83.1), (50.7, 93.5), (114.0, 3.9), (63.3, 88.3), (88.7, 77.9), (126.7, 62.4), (25.3, 103.8), (0.0, 114.2), (101.3, 72.8), (38.0, 98.7), (12.7, 109.0)]
IV: [(50.7, 96.3), (50.7, 108.6), (50.7, 79.4), (50.7, 62.4), (50.7, 67.9), (50.7, 89.4), (50.7, 116.2), (190.0, 7.5), (50.7, 111.6), (50.7, 76.3), (50.7, 91.7)]
เส้นถดถอยร่วม y = 3.00 + 0.50x
x=4 -> px (0.0, 120.0)
x=19 -> px (190.0, 7.5)10.3 คำตอบของปริศนาข้อที่สาม
คำถามที่ปลูกไว้ตั้งแต่บทที่ 1 คือ ถ้าตาถอดค่าจากกราฟได้ไม่แม่น แล้วเราจะเชื่อกราฟไปทำไม
คำตอบคือ กราฟกับตัวเลขไม่ได้ทำงานเดียวกัน
| ตัวเลข | ภาพ |
|---|---|
| แม่นทุกหลัก | อ่านค่าได้ไม่แม่น |
| บอกรูปร่างไม่ได้เลย | บอกรูปร่างได้ทันที |
| ซ่อนจุดผิดปกติ | จุดผิดปกติเด้งขึ้นมาเอง (บทที่ 2) |
| ตอบว่า "เท่าไหร่" | ตอบว่า "มันเป็นแบบไหน" |
การถอดรหัสที่ไม่แม่นซึ่งเราไล่มาเก้าบท คือราคาของคอลัมน์ขวา และมันคุ้ม เพราะคอลัมน์ซ้ายไม่มีทางบอกคุณได้เลยว่าข้อมูลของคุณเป็นชุด I หรือชุด IV
ตัวเลขบอกว่าเท่าไหร่ ภาพบอกว่ามันเป็นแบบไหน — และคุณผิดพลาดได้ราคาแพงกว่ามาก เวลาไม่รู้ว่ามันเป็นแบบไหน
10.4 นิสัยเดียวที่ควรติดตัวจากบทนี้
วาดก่อนสรุปเสมอ แม้จะไม่มีใครได้เห็นภาพนั้นเลยก็ตาม
ภาพที่คุณวาดให้ตัวเองดูสามสิบวินาทีก่อนส่งรายงาน ไม่ต้องสวย ไม่ต้องมีหัวข้อ ไม่ต้องเลือกสี มันมีหน้าที่เดียวคือบอกคุณว่าค่าสรุปที่คุณกำลังจะเขียนลงไปนั้น อธิบายข้อมูลจริงหรือกำลังปิดบังมันอยู่
LEVEL 3 · ระดับสูง
ตาเดินยังไงบนหน้าเดียว#
สิบบทแรกพูดถึงการเข้ารหัสค่าหนึ่งชุดด้วยช่องทางหนึ่งช่อง ตั้งแต่บทนี้ไปโจทย์เปลี่ยน — บนหน้ากระดาษจริงมีของหลายชิ้นอยู่พร้อมกัน และสายตาจะจัดกลุ่มมันให้เสร็จก่อนที่คุณจะได้พูดอะไรสักคำ
คำถามของบทนี้จึงไม่ใช่ "จะเข้ารหัสยังไง" อีกต่อไป แต่เป็น "ใครเป็นคนตัดสินว่าอะไรอยู่พวกเดียวกัน"
11.1 ระยะห่างพูดแทนคุณ ไม่ว่าคุณจะตั้งใจหรือไม่
สองแถวนี้มีของแปดชิ้นเท่ากัน ไม่มีตัวเลขไหนต่างกันเลย แต่แถวบนคุณเห็นเป็นสี่กลุ่มทันทีโดยไม่มีเส้น ไม่มีกรอบ ไม่มีสีมาช่วย ส่วนแถวล่างเห็นเป็นแปดชิ้นที่ไม่เกี่ยวอะไรกัน
สิ่งเดียวที่เปลี่ยนคือระยะห่าง — ซึ่ง คุณเป็นคนกำหนดทุกครั้งที่วางของลงบนสไลด์
ทุกครั้งที่คุณวางของสองชิ้นไว้ใกล้กัน คุณกำลังบอกคนอ่านว่ามันเป็นพวกเดียวกัน
นี่คือเหตุผลที่คำอธิบายสัญลักษณ์ในบทที่ 2 ถึงแพ้การติดป้ายใต้แท่ง ไม่ใช่แค่เพราะต้องกวาดสายตาไปกลับ แต่เพราะระยะห่างกำลังบอกว่าป้ายไม่ได้เป็นพวกเดียวกับแท่ง ทั้งที่มันคือของชิ้นเดียวกัน
11.2 กฎการจับกลุ่มที่ตาใช้
ความใกล้เป็นตัวที่แรงที่สุด แต่ไม่ใช่ตัวเดียว หลักการจัดกลุ่มของสายตาถูกรวบรวมไว้ตั้งแต่ต้นศตวรรษที่ 20 โดยกลุ่มนักจิตวิทยาสาย Gestalt และที่ใช้ได้จริงในงานกราฟมีอยู่ห้าข้อ
| กฎ | ตาจะจับเป็นพวกเดียวกันเมื่อ | ใช้ทำอะไรได้ |
|---|---|---|
| ความใกล้ | อยู่ใกล้กันกว่าของอื่น | จัดกลุ่มโดยไม่ต้องวาดอะไรเพิ่มเลย |
| ความคล้าย | หน้าตาเหมือนกัน (สี ขนาด รูปทรง) | โยงของที่อยู่คนละที่ให้เป็นชุดเดียว |
| การล้อมกรอบ | อยู่ในกรอบหรือแถบสีเดียวกัน | แรงที่สุด แต่กินหมึกมากที่สุดด้วย |
| ความต่อเนื่อง | เรียงตัวอยู่บนแนวเดียวกัน | ทำให้ตาไหลไปตามเส้นแนวโน้ม |
| การเคลื่อนไหวร่วม | ขยับไปทางเดียวกัน | ใช้ได้เฉพาะบนจอ ไม่ใช่บนกระดาษ |
สังเกตว่าสามข้อแรกเรียงจากถูกที่สุดไปแพงที่สุด ความใกล้ไม่กินหมึกเลยสักหยด ความคล้ายกินช่องทางหนึ่งช่อง ส่วนการล้อมกรอบกินทั้งหมึกและช่องทาง
บทที่ 7 บอกให้ลบหมึกที่ไม่บอกอะไร บทนี้บอกว่าลบยังไงไม่ให้เสียการจัดกลุ่ม — คำตอบคือแทนที่กรอบด้วยระยะห่าง ซึ่งให้ผลเหมือนกันโดยไม่เสียหมึกสักหยด
11.3 ลองขยับระยะห่างเอง
"""ความใกล้ตัดสินการจับกลุ่ม — ข้อมูลชุดเดิม เปลี่ยนแค่ระยะห่าง แล้วกลุ่มที่ตาเห็นเปลี่ยนตาม"""
LABELS = ["ก", "ข", "ค", "ง", "จ", "ฉ", "ช", "ซ"]
def layout(gap_in, gap_between):
"""วาง 8 จุดเป็น 4 คู่ · gap_in = ระยะในคู่ · gap_between = ระยะระหว่างคู่"""
xs, x = [], 0.0
for i in range(len(LABELS)):
xs.append(x)
x += gap_in if i % 2 == 0 else gap_between
return xs
def clusters(xs, threshold):
"""จับกลุ่มแบบ single-linkage: ห่างกันไม่เกิน threshold = กลุ่มเดียวกัน"""
groups, cur = [], [0]
for i in range(1, len(xs)):
if xs[i] - xs[i - 1] <= threshold:
cur.append(i)
else:
groups.append(cur)
cur = [i]
groups.append(cur)
return groups
def show(name, gap_in, gap_between, threshold):
xs = layout(gap_in, gap_between)
gs = clusters(xs, threshold)
print(f"=== {name} ===")
print(f" ระยะในคู่ {gap_in} px · ระหว่างคู่ {gap_between} px · เกณฑ์สายตา {threshold} px")
print(f" ตำแหน่ง: {[round(v) for v in xs]}")
print(f" ตาเห็นเป็น {len(gs)} กลุ่ม: "
+ " | ".join("".join(LABELS[i] for i in g) for g in gs))
return xs
show("จัดชิดเป็นคู่", 26, 74, 40)
print()
show("จัดห่างเท่ากันหมด", 50, 50, 40)
print()
print("ข้อมูลเหมือนกันทั้งสองแบบ — 8 รายการชุดเดิม ไม่มีตัวเลขไหนเปลี่ยน")
print("สิ่งที่เปลี่ยนคือระยะห่าง ซึ่งคนทำภาพเป็นคนกำหนด และตาจับกลุ่มตามนั้นทันที")ผลรันจริง
=== จัดชิดเป็นคู่ ===
ระยะในคู่ 26 px · ระหว่างคู่ 74 px · เกณฑ์สายตา 40 px
ตำแหน่ง: [0, 26, 100, 126, 200, 226, 300, 326]
ตาเห็นเป็น 4 กลุ่ม: กข | คง | จฉ | ชซ
=== จัดห่างเท่ากันหมด ===
ระยะในคู่ 50 px · ระหว่างคู่ 50 px · เกณฑ์สายตา 40 px
ตำแหน่ง: [0, 50, 100, 150, 200, 250, 300, 350]
ตาเห็นเป็น 8 กลุ่ม: ก | ข | ค | ง | จ | ฉ | ช | ซ
ข้อมูลเหมือนกันทั้งสองแบบ — 8 รายการชุดเดิม ไม่มีตัวเลขไหนเปลี่ยน
สิ่งที่เปลี่ยนคือระยะห่าง ซึ่งคนทำภาพเป็นคนกำหนด และตาจับกลุ่มตามนั้นทันที⚠️ ค่า threshold ในสคริปต์เป็นแบบจำลองของเกณฑ์สายตา ไม่ใช่ค่าที่วัดจากคน ของจริงขึ้นกับขนาดภาพและระยะมอง สิ่งที่สคริปต์นี้พิสูจน์ได้จริงคือ ความสัมพันธ์ — พอสัดส่วนระยะเปลี่ยน จำนวนกลุ่มเปลี่ยนตามแบบไม่ต่อเนื่อง ไม่ใช่ค่อย ๆ เปลี่ยน
11.4 ภาพชุดชนะภาพเดียวที่ยัดทุกอย่าง
พอรู้ว่าตาจับกลุ่มด้วยความใกล้ ทางออกของ "ข้อมูลเยอะเกินกราฟเดียว" ก็เปลี่ยนไป
วิธีที่คนทำกันคือยัดทุกเส้นลงกราฟเดียว แล้วแยกด้วยสี ซึ่งพังสองชั้นพร้อมกัน — บทที่ 2 บอกว่าเกินสองสามสีก็เลิกเด้ง และบทที่ 5 บอกว่าสีอยู่ขั้นล่างสุดของบันได
ทางที่ตรงกับกลไกมากกว่าคือ แยกเป็นภาพเล็กหลายภาพที่ใช้แกนเดียวกัน วางเรียงกัน แต่ละภาพมีเส้นเดียว ผลคือการเทียบข้ามภาพกลายเป็นการเทียบ ตำแหน่งบนแกนคนละตัว ซึ่งเป็นขั้นที่สองของบันได แทนที่จะเป็นการแยกสีซึ่งอยู่ขั้นล่างสุด
ขึ้นจากขั้น 7 ไปขั้น 2 ของบันได โดยไม่ได้เปลี่ยนข้อมูลเลยสักตัว — เงื่อนไขเดียวคือทุกภาพต้องใช้แกนเดียวกันเป๊ะ ไม่งั้นการเทียบข้ามภาพจะโกหกทันที ด้วยกลไกเดียวกับบทที่ 9
โครงเรื่องคือลำดับที่ตาเดิน#
บทที่แล้วจบด้วยคำถามที่ยังไม่ได้ตอบ — ถ้าสายตาจัดกลุ่มตามที่เราวาง แล้ว เราควรวางให้มันเห็นเป็นกลุ่มอะไร
คำตอบคือสองเครื่องมือที่คุณน่าจะเคยได้ยินชื่อมาแล้ว และน่าจะเคยถูกสอนในฐานะกฎที่ต้องท่อง บทนี้จะไม่สอนแบบนั้น เพราะพอมาถึงตรงนี้ ทั้งสองอย่างอธิบายได้ด้วยกลไกที่คุณมีอยู่แล้วทั้งหมด
12.1 Pyramid — ข้อสรุปขึ้นก่อน เพราะความสนใจมีจำกัด
หลักการพีระมิดบอกว่า ให้ขึ้นต้นด้วยข้อสรุป แล้วค่อยตามด้วยเหตุผลที่รองรับมัน ตรงข้ามกับลำดับที่เราคิดจริง ๆ ซึ่งมักเป็นการไล่หลักฐานก่อนแล้วค่อยสรุป
เหตุผลไม่ได้อยู่ที่ธรรมเนียมการเขียน แต่อยู่ที่บทที่ 2 — ความสนใจเป็นทรัพยากรจำกัดและมันถูกใช้ไปเรื่อย ๆ คนอ่านที่ยังไม่รู้ว่ากำลังจะไปไหน ต้องแบกหลักฐานทุกชิ้นไว้ในหัวเผื่อว่ามันจะสำคัญ พอถึงข้อสรุปก็เหนื่อยเกินกว่าจะประเมินมันแล้ว
พูดด้วยภาษาของเล่มนี้คือ ข้อสรุปที่มาทีหลังบังคับให้คนอ่านทำงานโหมดไล่ทีละชิ้นกับทุกย่อหน้า ส่วนข้อสรุปที่มาก่อนเปลี่ยนงานที่เหลือเป็นการตรวจสอบ ซึ่งเบากว่ามาก
นี่คือเรื่องเดียวกับหัวสไลด์ที่บอกข้อสรุปใน FIG 1.1 เป๊ะ ๆ — แค่ขยายจากหนึ่งสไลด์เป็นทั้งเอกสาร
12.2 MECE — การปูกระเบื้อง ไม่ใช่คำศัพท์ที่ปรึกษา
💡 MECE — ย่อจาก Mutually Exclusive, Collectively Exhaustive · แปลว่ากลุ่มที่คุณแบ่ง ต้องไม่ทับกัน และ ต้องครอบคลุมทุกอย่างในขอบเขต · แนวคิดนี้ผูกกับหลักพีระมิดมาตั้งแต่ต้น เพราะเหตุผลที่รองรับข้อสรุปจะเชื่อถือได้ก็ต่อเมื่อมันไม่นับซ้ำและไม่ตกหล่น
ทำไมมันถึงสำคัญกับสายตา ไม่ใช่แค่กับตรรกะ — เพราะบทที่ 11 บอกแล้วว่าตาจับกลุ่มเสร็จก่อนคุณพูด ถ้ารายการหนึ่งอยู่ได้สองกลุ่ม สายตาจะต้องเลือกให้มันกลุ่มเดียว แล้วมันจะเลือกโดยที่คุณไม่รู้ว่าเลือกอันไหน
ที่แย่กว่านั้นคือของที่ตกหล่น มันไม่ได้ขึ้นมาบอกว่าหายไป ช่องว่างไม่มีรูปร่าง ตาจึงมองไม่เห็นมัน — คนอ่านจะเชื่อว่าภาพครบ ทั้งที่ขาดไปหนึ่งในสี่
12.3 ตรวจ MECE ด้วยเครื่อง แทนที่จะเพ่งเอา
ข่าวดีคือทั้งสองเงื่อนไขเป็นคำถามที่โปรแกรมตอบได้ ไม่ต้องใช้วิจารณญาณเลย
"""ตรวจ MECE ด้วยเครื่อง — ซ้ำกันไหม และตกหล่นไหม เป็นคำถามที่โปรแกรมตอบได้"""
ALL = ["ค่าจ้าง", "ค่าล่วงเวลา", "ค่าไฟ", "ค่าน้ำ", "ค่าอินเทอร์เน็ต",
"ค่าซ่อมลิฟต์", "ค่าซ่อมแอร์", "กระดาษ", "หมึกพิมพ์",
"ค่าอบรม", "ค่าเดินทางไปอบรม", "ค่าที่ปรึกษา"]
DRAFT = {
"ค่าคน": ["ค่าจ้าง", "ค่าล่วงเวลา", "ค่าอบรม", "ค่าที่ปรึกษา"],
"ค่าอาคาร": ["ค่าไฟ", "ค่าน้ำ", "ค่าซ่อมลิฟต์", "ค่าซ่อมแอร์"],
"ค่าพัฒนาบุคลากร": ["ค่าอบรม", "ค่าเดินทางไปอบรม"],
}
FIXED = {
"จ่ายให้คนของเรา": ["ค่าจ้าง", "ค่าล่วงเวลา", "ค่าอบรม", "ค่าเดินทางไปอบรม"],
"จ่ายให้คนนอก": ["ค่าที่ปรึกษา"],
"จ่ายให้อาคารและระบบ": ["ค่าไฟ", "ค่าน้ำ", "ค่าอินเทอร์เน็ต",
"ค่าซ่อมลิฟต์", "ค่าซ่อมแอร์"],
"ของใช้สิ้นเปลือง": ["กระดาษ", "หมึกพิมพ์"],
}
def check(name, groups, universe):
print(f"=== {name} ===")
seen = {}
for g, items in groups.items():
for it in items:
seen.setdefault(it, []).append(g)
overlap = {k: v for k, v in seen.items() if len(v) > 1}
missing = [it for it in universe if it not in seen]
stray = [it for it in seen if it not in universe]
print(f" กลุ่ม {len(groups)} กลุ่ม · ครอบคลุม {len(seen)} จาก {len(universe)} รายการ")
if overlap:
print(" ไม่ Mutually Exclusive — รายการที่อยู่หลายกลุ่ม:")
for it, gs in overlap.items():
print(f" {it} อยู่ใน {' + '.join(gs)}")
else:
print(" Mutually Exclusive: ผ่าน ไม่มีรายการไหนอยู่สองกลุ่ม")
if missing:
print(f" ไม่ Collectively Exhaustive — ตกหล่น {len(missing)} รายการ: {', '.join(missing)}")
else:
print(" Collectively Exhaustive: ผ่าน ไม่มีรายการตกหล่น")
if stray:
print(f" มีรายการนอกขอบเขต: {', '.join(stray)}")
ok = not overlap and not missing and not stray
print(f" สรุป: {'MECE' if ok else 'ยังไม่ MECE'}\n")
return ok
check("ร่างแรก จัดตามสัญชาตญาณ", DRAFT, ALL)
check("ร่างสอง ถามว่าเงินออกไปหาใคร", FIXED, ALL)
print("ร่างแรกไม่ได้ขี้เกียจ มันแค่ใช้เกณฑ์แบ่งสองอันปนกัน")
print("คือ 'ประเภทของต้นทุน' กับ 'วัตถุประสงค์' ซึ่งซ้อนทับกันโดยธรรมชาติ")
print("ร่างสองใช้เกณฑ์เดียวตลอด: เงินก้อนนี้ออกไปหาใคร")
# --- เรขาคณิตของ FIG 12.1: ขอบเขตทั้งหมด = 560 px ---
W = 560.0
unit = W / len(ALL)
print(f"\n=== เรขาคณิตของ FIG 12.1 (1 รายการ = {unit:.2f} px จาก {len(ALL)} รายการ) ===")
print("ร่างแรก — วางให้เห็นการทับซ้อนกับช่องโหว่")
# วางกลุ่มที่ใช้รายการร่วมกันให้ติดกัน ไม่งั้นภาพจะโชว์การทับซ้อนผิดคู่
LAYOUT = ["ค่าคน", "ค่าพัฒนาบุคลากร", "ค่าอาคาร"]
covered = set()
x = 0.0
for g in LAYOUT:
items = DRAFT[g]
dup = [it for it in items if it in covered]
start = x - len(dup) * unit # ถอยหลังเท่าจำนวนรายการที่ซ้ำ เพื่อให้บล็อกทับกันจริง
w = len(items) * unit
print(f" {g:<16} x={start:6.2f} w={w:6.2f} (ซ้ำ {len(dup)} รายการ)")
covered.update(items)
x = start + w
gap_items = len(ALL) - len(covered)
print(f" ช่องโหว่ท้ายแถบ x={x:.2f} w={gap_items * unit:.2f} ({gap_items} รายการที่ไม่มีกลุ่มไหนรับ)")
print("ร่างสอง — ปูเรียงกันไปโดยไม่ทับ")
x = 0.0
for g, items in FIXED.items():
w = len(items) * unit
print(f" {g:<22} x={x:6.2f} w={w:6.2f}")
x += w
print(f" ปลายแถบอยู่ที่ {x:.2f} px = เต็มขอบเขต {W:.0f} px พอดี")ผลรันจริง
=== ร่างแรก จัดตามสัญชาตญาณ ===
กลุ่ม 3 กลุ่ม · ครอบคลุม 9 จาก 12 รายการ
ไม่ Mutually Exclusive — รายการที่อยู่หลายกลุ่ม:
ค่าอบรม อยู่ใน ค่าคน + ค่าพัฒนาบุคลากร
ไม่ Collectively Exhaustive — ตกหล่น 3 รายการ: ค่าอินเทอร์เน็ต, กระดาษ, หมึกพิมพ์
สรุป: ยังไม่ MECE
=== ร่างสอง ถามว่าเงินออกไปหาใคร ===
กลุ่ม 4 กลุ่ม · ครอบคลุม 12 จาก 12 รายการ
Mutually Exclusive: ผ่าน ไม่มีรายการไหนอยู่สองกลุ่ม
Collectively Exhaustive: ผ่าน ไม่มีรายการตกหล่น
สรุป: MECE
ร่างแรกไม่ได้ขี้เกียจ มันแค่ใช้เกณฑ์แบ่งสองอันปนกัน
คือ 'ประเภทของต้นทุน' กับ 'วัตถุประสงค์' ซึ่งซ้อนทับกันโดยธรรมชาติ
ร่างสองใช้เกณฑ์เดียวตลอด: เงินก้อนนี้ออกไปหาใคร
=== เรขาคณิตของ FIG 12.1 (1 รายการ = 46.67 px จาก 12 รายการ) ===
ร่างแรก — วางให้เห็นการทับซ้อนกับช่องโหว่
ค่าคน x= 0.00 w=186.67 (ซ้ำ 0 รายการ)
ค่าพัฒนาบุคลากร x=140.00 w= 93.33 (ซ้ำ 1 รายการ)
ค่าอาคาร x=233.33 w=186.67 (ซ้ำ 0 รายการ)
ช่องโหว่ท้ายแถบ x=420.00 w=140.00 (3 รายการที่ไม่มีกลุ่มไหนรับ)
ร่างสอง — ปูเรียงกันไปโดยไม่ทับ
จ่ายให้คนของเรา x= 0.00 w=186.67
จ่ายให้คนนอก x=186.67 w= 46.67
จ่ายให้อาคารและระบบ x=233.33 w=233.33
ของใช้สิ้นเปลือง x=466.67 w= 93.33
ปลายแถบอยู่ที่ 560.00 px = เต็มขอบเขต 560 px พอดี12.4 บทเรียนที่ตัวสคริปต์สอน แต่กฎไม่เคยสอน
ร่างแรกไม่ได้เกิดจากความมักง่าย มันเกิดจากการใช้เกณฑ์แบ่งสองอันปนกันในชั้นเดียว — "ประเภทของต้นทุน" กับ "วัตถุประสงค์" ซึ่งซ้อนทับกันโดยธรรมชาติ ค่าอบรมเป็นทั้งค่าคนและค่าพัฒนาบุคลากรพร้อมกัน มันจึงไปโผล่สองที่
พอเปลี่ยนเป็นเกณฑ์เดียวคือ "เงินก้อนนี้ออกไปหาใคร" ทุกอย่างเข้าที่เอง โดยไม่ต้องนั่งไล่ตรวจทีละรายการ
ถ้าจัดกลุ่มแล้วมีของอยู่ได้สองที่ อย่าไปเถียงว่ามันควรอยู่ที่ไหน — ให้กลับไปดูว่าคุณใช้เกณฑ์แบ่งกี่อัน
นี่คือสิ่งที่การท่องคำว่า MECE ให้ไม่ได้ ถ้าคุณจำแค่ "ห้ามทับกัน ห้ามตกหล่น" คุณจะพยายามซ่อมด้วยการย้ายรายการไปมา ซึ่งไม่มีวันจบ แต่ถ้าคุณรู้ว่าการทับกันคืออาการของเกณฑ์ที่ปนกัน คุณจะรู้ว่าต้องกลับไปแก้ที่ชั้นบน
12.5 สองอย่างนี้ประกอบกันเป็นอะไร
พีระมิดบอกลำดับแนวตั้ง — อะไรมาก่อนอะไร · MECE บอกการแบ่งแนวนอน — อะไรอยู่พวกเดียวกัน
พอเอามารวมกัน คุณจะได้สิ่งที่บทที่ 11 ต้องการพอดี คือ แผนที่ว่าตาควรเดินยังไงบนหน้านี้ ข้อสรุปอยู่บนสุดเพราะตาไปถึงก่อน กลุ่มที่ไม่ทับกันเรียงอยู่ใต้มันเพราะตาจับกลุ่มด้วยความใกล้ และไม่มีช่องว่างให้ตาตกหล่น
บทถัดไปจะบีบทั้งหมดนี้ลงเหลือประโยคเดียว
หัวข้อเดียวที่พูดแทนทั้งกราฟ#
บทที่แล้วจบด้วยคำสัญญาว่าจะบีบทุกอย่างลงเหลือประโยคเดียว บทนี้คือประโยคนั้น
และมันไม่ใช่เรื่องของการเขียนให้เก่ง — เพราะถึงคุณจะเลือกช่องทางถูกทุกข้อตามสิบสองบทที่ผ่านมา ก็ยังมีข้อเท็จจริงบางข้อที่ไม่มีช่องทางไหนแบกไหว ข้อพวกนั้นไม่ได้หายไปไหน มันรอคุณอยู่ที่บรรทัดบนสุดของสไลด์
13.1 ช่องทางที่ไม่เคยอยู่ในบันได
บทที่ 4 นับช่องทางเข้ารหัสได้เจ็ดอย่าง บทที่ 5 เอามาเรียงเป็นบันไดเจ็ดขั้นตามความแม่นที่วัดกับคนจริง แล้วทั้งเล่มก็ไต่บันไดนั้นมาตลอดสิบบท
แต่บนสไลด์จริงมีของอีกชิ้นที่แบกข้อมูลอยู่ทุกใบ และมันไม่เคยโผล่ในลิสต์ทั้งสองอันเลย — ตัวหนังสือ
เหตุผลที่มันไม่อยู่ในลิสต์นั้นตรงไปตรงมา ลิสต์ของบทที่ 4 นับเฉพาะคุณสมบัติที่ แปลงค่าเป็นรูปทรง ได้แก่ความยาว มุม พื้นที่ ความเข้ม ส่วนตัวหนังสือไม่ได้แปลงอะไรเลย มันเขียนเลข 25 ลงไปตรง ๆ แล้วคนอ่านก็ได้เลข 25 กลับไป
พูดด้วยภาษาของสายพานในบทที่ 3 คือ ตัวหนังสือข้ามขั้นถอดรหัสเชิงเรขาคณิตไปทั้งขั้น ไม่มีการบีบ ไม่มีการขยาย ไม่มีความคลาดเคลื่อนแบบที่เราวัดมาสิบบท
ตัวหนังสือไม่ได้อยู่บนบันได เพราะมันไม่ได้ปีนบันไดตั้งแต่แรก มันเดินอ้อมไป
ถ้าจบตรงนี้ คำตอบก็ควรจะเป็น "งั้นก็เขียนตัวเลขทั้งหมดลงไปสิ" ซึ่งผิด และเหตุผลที่ผิดอยู่ในบทที่ 2
การอ่านตัวหนังสือคืองานโหมดไล่ทีละชิ้นเต็มตัว คนอ่านต้องกวาดสายตาไปทีละคำ เวลาที่ใช้แปรตามจำนวนคำเกือบเป็นเส้นตรง ตรงข้ามกับกราฟที่ส่งรูปร่างทั้งหน้ามาถึงตาพร้อมกัน
นั่นคือค่าผ่านทางของทางอ้อม — ตัวหนังสือแม่นที่สุด แต่ขยายไม่ได้ ภาพหนึ่งภาพรับข้อมูลได้เป็นพัน ส่วนหัวสไลด์รับได้ประโยคเดียว
💡 ทำไมถึงเป็น "ประโยคเดียว" ไม่ใช่ "ย่อหน้าเดียว" — ไม่ใช่กฎการเขียน แต่มาจากบทที่ 12 · หลักพีระมิดบอกว่าข้อสรุปต้องมาก่อน และความสนใจของคนอ่านถูกใช้ไปเรื่อย ๆ · ทุกประโยคที่คุณเพิ่มในหัวสไลด์ คือการใช้ทรัพยากรก้อนเดียวกับที่กราฟข้างล่างต้องใช้
13.2 ข้อเท็จจริงไหนที่ภาพแบกไหว ข้อไหนที่ไม่ไหว
คำถามที่ใช้ได้จริงจึงไม่ใช่ "หัวข้อควรเขียนว่าอะไร" แต่เป็น "ภาพข้างล่างแบกอะไรไม่ไหวบ้าง" ซึ่งเป็นคำถามที่คำนวณได้ตรง ๆ ด้วยเครื่องมือที่มีอยู่แล้วตั้งแต่บทที่ 5
ชุดข้อมูลเดิมของเราให้ข้อเท็จจริงห้าข้อ คือช่องว่างระหว่างอันดับที่ติดกันสี่คู่ บวกช่วงจากหัวตารางถึงท้ายตารางอีกหนึ่ง สคริปต์ข้างล่าง (headline.py) เอาแต่ละข้อไปหารด้วยช่วงที่ช่องทางมีให้ แล้วตัดสินด้วยเกณฑ์ K ตัวเดียวกับ channels.py ในบทที่ 5
"""หัวข้อคือที่เก็บของข้อเท็จจริงที่ช่องทางภาพแบกไม่ไหว — และคำนวณได้ว่ามีข้อไหนบ้าง"""
DATA = [("ก", 22), ("ข", 18), ("ค", 25), ("ง", 16), ("จ", 19)]
K = 0.05 # เกณฑ์เดียวกับ channels.py ในบทที่ 5 — ช่องว่างต้องกินกี่ส่วนของช่วง ถึงจะหวังให้ตาแยกออก
vals = [v for _, v in DATA]
hi, total = max(vals), sum(vals)
rank = sorted(DATA, key=lambda kv: -kv[1])
def share_of_range(gap, channel):
"""ช่องว่าง gap หน่วย ได้ส่วนแบ่งเท่าไหร่ของช่วงที่ช่องทางนั้นมีให้ — สูตรเดียวกับบทที่ 5"""
return gap / hi if channel == "ความยาว" else gap / total
# --- 1. ข้อเท็จจริงของสไลด์ ทุกข้อคือช่องว่างระหว่างค่าสองค่า ---
# คู่ที่ติดกันในอันดับทุกคู่ บวกคู่หัว-ท้ายซึ่งเป็นช่วงกว้างสุดของชุด
steps = [(rank[i], rank[i + 1]) for i in range(len(rank) - 1)]
FACTS = [(f"อันดับ {i + 1} กับ {i + 2}", a, b) for i, (a, b) in enumerate(steps)]
FACTS.append(("หัวตารางกับท้ายตาราง", rank[0], rank[-1]))
# --- 2. ช่องทางไหนแสดงข้อไหนได้เอง ข้อที่เหลือคือภาระของหัวข้อ ---
CHANNELS = ["ความยาว", "มุม"]
homeless = {ch: [] for ch in CHANNELS}
print("=== ข้อเท็จจริงห้าข้อ ผ่านสองช่องทาง ===")
for label, a, b in FACTS:
gap = a[1] - b[1]
cells = []
for ch in CHANNELS:
share = share_of_range(gap, ch)
if share < K:
homeless[ch].append((a[0], b[0], gap))
cells.append(f"{ch} {share * 100:5.2f}% {'ภาพแสดงได้' if share >= K else 'ภาพแสดงไม่ได้'}")
print(f" {label} (หน่วย {a[0]} {a[1]} กับ หน่วย {b[0]} {b[1]} · ต่าง {gap} จุด)")
for cell in cells:
print(f" {cell}")
# --- 3. ประกอบประโยค: ข้อสรุปขึ้นก่อน แล้วต่อด้วยข้อที่ภาพแบกไม่ไหว ---
def compose(channel):
parts = [f"หน่วย {rank[0][0]} ใช้มากที่สุด {rank[0][1]} จาก {total}"]
parts += [f"{a} กับ {b} ต่างกัน {gap} จุด" for a, b, gap in homeless[channel]]
return " · ".join(parts)
print("\n=== หัวข้อที่ประกอบจากข้อที่เหลือ ===")
for ch in CHANNELS:
line = compose(ch)
print(f" ช่องทาง {ch}: ตกค้าง {len(homeless[ch])} จาก {len(FACTS)} ข้อ "
f"· หัวข้อยาว {len(line)} ตัวอักษร")
print(f" {line}")
extra = len(compose("มุม")) - len(compose("ความยาว"))
print(f"\nย้ายไปช่องทางที่แย่กว่า หัวข้อยาวขึ้น {extra} ตัวอักษร โดยข้อมูลไม่ขยับเลยสักตัว")
print("หัวข้อที่ยาวขึ้นเรื่อย ๆ จึงไม่ใช่ปัญหาการเขียน แต่เป็นใบเสร็จของช่องทางที่เลือก")
# --- 4. ตัวเลขทุกตัวที่ปรากฏใน FIG 13.1 ---
print("\n=== ตัวเลขทุกตัวใน FIG 13.1 ===")
for label, a, b in FACTS:
gap = a[1] - b[1]
cells = " | ".join(
f"{ch} {share_of_range(gap, ch) * 100:.2f}%"
f"{'' if share_of_range(gap, ch) >= K else ' (ตก)'}"
for ch in CHANNELS
)
print(f" แถว '{label}' {gap} จุด: {cells}")
for ch in CHANNELS:
print(f" หัวข้อของช่องทาง {ch}: ตกค้าง {len(homeless[ch])} ข้อ · {len(compose(ch))} ตัวอักษร")ผลรันจริง
=== ข้อเท็จจริงห้าข้อ ผ่านสองช่องทาง ===
อันดับ 1 กับ 2 (หน่วย ค 25 กับ หน่วย ก 22 · ต่าง 3 จุด)
ความยาว 12.00% ภาพแสดงได้
มุม 3.00% ภาพแสดงไม่ได้
อันดับ 2 กับ 3 (หน่วย ก 22 กับ หน่วย จ 19 · ต่าง 3 จุด)
ความยาว 12.00% ภาพแสดงได้
มุม 3.00% ภาพแสดงไม่ได้
อันดับ 3 กับ 4 (หน่วย จ 19 กับ หน่วย ข 18 · ต่าง 1 จุด)
ความยาว 4.00% ภาพแสดงไม่ได้
มุม 1.00% ภาพแสดงไม่ได้
อันดับ 4 กับ 5 (หน่วย ข 18 กับ หน่วย ง 16 · ต่าง 2 จุด)
ความยาว 8.00% ภาพแสดงได้
มุม 2.00% ภาพแสดงไม่ได้
หัวตารางกับท้ายตาราง (หน่วย ค 25 กับ หน่วย ง 16 · ต่าง 9 จุด)
ความยาว 36.00% ภาพแสดงได้
มุม 9.00% ภาพแสดงได้
=== หัวข้อที่ประกอบจากข้อที่เหลือ ===
ช่องทาง ความยาว: ตกค้าง 1 จาก 5 ข้อ · หัวข้อยาว 55 ตัวอักษร
หน่วย ค ใช้มากที่สุด 25 จาก 100 · จ กับ ข ต่างกัน 1 จุด
ช่องทาง มุม: ตกค้าง 4 จาก 5 ข้อ · หัวข้อยาว 127 ตัวอักษร
หน่วย ค ใช้มากที่สุด 25 จาก 100 · ค กับ ก ต่างกัน 3 จุด · ก กับ จ ต่างกัน 3 จุด · จ กับ ข ต่างกัน 1 จุด · ข กับ ง ต่างกัน 2 จุด
ย้ายไปช่องทางที่แย่กว่า หัวข้อยาวขึ้น 72 ตัวอักษร โดยข้อมูลไม่ขยับเลยสักตัว
หัวข้อที่ยาวขึ้นเรื่อย ๆ จึงไม่ใช่ปัญหาการเขียน แต่เป็นใบเสร็จของช่องทางที่เลือก
=== ตัวเลขทุกตัวใน FIG 13.1 ===
แถว 'อันดับ 1 กับ 2' 3 จุด: ความยาว 12.00% | มุม 3.00% (ตก)
แถว 'อันดับ 2 กับ 3' 3 จุด: ความยาว 12.00% | มุม 3.00% (ตก)
แถว 'อันดับ 3 กับ 4' 1 จุด: ความยาว 4.00% (ตก) | มุม 1.00% (ตก)
แถว 'อันดับ 4 กับ 5' 2 จุด: ความยาว 8.00% | มุม 2.00% (ตก)
แถว 'หัวตารางกับท้ายตาราง' 9 จุด: ความยาว 36.00% | มุม 9.00%
หัวข้อของช่องทาง ความยาว: ตกค้าง 1 ข้อ · 55 ตัวอักษร
หัวข้อของช่องทาง มุม: ตกค้าง 4 ข้อ · 127 ตัวอักษรบรรทัดที่ควรอ่านซ้ำคือสองบรรทัดสุดท้ายของหัวข้อ 55 ตัวอักษรกับ 127 ตัวอักษร ข้อมูลชุดเดียวกันเป๊ะ แต่พอเปลี่ยนช่องทางจากแท่งเป็นวงกลม หัวข้อต้องยาวขึ้นเกินสองเท่าเพื่อจะพูดเรื่องเดิมให้ครบ
เหตุผลไม่ได้อยู่ที่การเขียน มันอยู่ที่ว่าวงกลมส่งข้อเท็จจริงถึงคนอ่านได้แค่ข้อเดียวจากห้าข้อ ที่เหลืออีกสี่ข้อจึงตกมาให้ประโยคเดียวนั้นแบกทั้งหมด
ถ้าหัวข้อของคุณยาวขึ้นเรื่อย ๆ นั่นไม่ใช่ปัญหาการเขียน มันคือใบเสร็จว่าภาพข้างล่างแบกไม่ไหว
13.3 ประกอบเป็นประโยคเดียว
สคริปต์ข้างบนประกอบประโยคด้วยกฎสองข้อ และทั้งสองข้อมาจากบทก่อนหน้าทั้งคู่
ข้อแรก ขึ้นต้นด้วยข้อสรุป — มาจากหลักพีระมิดในบทที่ 12 คนอ่านที่ยังไม่รู้ว่ากำลังจะไปไหน ต้องแบกทุกอย่างไว้ในหัวเผื่อว่ามันจะสำคัญ
ข้อสอง ต่อด้วยข้อที่ภาพแสดงไม่ได้ — มาจากบทที่ 6 ช่องว่างที่เล็กกว่าเกณฑ์ไม่ได้แค่ "เห็นยาก" มันไม่ได้ถูกส่งไปถึงคนอ่านเลย ถ้าคุณไม่เขียน ก็ไม่มีใครรู้
ลองเทียบกับ FIG 1.1 ในบทแรกของเล่ม หัวข้อของแผงขวาคือ "หน่วย ค ใช้มากที่สุด แต่ห่างอันดับสองแค่ 3 จุด" ซึ่งตอนนั้นมันดูเหมือนการเขียนที่ดี ตอนนี้คุณเห็นแล้วว่ามันไม่ใช่รสนิยม — มันคือผลลัพธ์ที่คำนวณออกมาได้
⚠️ สิ่งที่สคริปต์ทำได้คือหาข้อเท็จจริง ไม่ใช่ถ้อยคำ ประโยคที่มันพิมพ์ออกมายังแข็งอยู่ และคุณควรเกลาให้อ่านลื่นขึ้น แต่กติกามีข้อเดียว — เกลาถ้อยคำได้ ห้ามเพิ่มข้อเท็จจริง เพราะทุกข้อในรายการมีที่มาจากตัวเลข ส่วนข้อที่คุณเติมเองไม่มี
เคล็ดลับ: ถ้าเขียนหัวข้อไม่ออก อย่าเพิ่งโทษตัวเอง ให้กลับไปดูก่อนว่าคุณตอบได้ไหมว่าช่องว่างที่แคบที่สุดในชุดคือเท่าไหร่ ถ้าตอบไม่ได้ แปลว่ายังไม่รู้ว่าข้อมูลกำลังบอกอะไร ไม่ใช่เขียนไม่เป็น
13.4 หัวข้อก็มีตัวคูณคำโกหกของมันเอง
มาถึงตรงนี้เล่มนี้เชียร์หัวข้อที่บอกข้อสรุปมาตลอด จึงต้องพูดอีกด้านให้ชัด
หัวข้อที่บอกข้อสรุปคือคำกล่าวอ้าง และคำกล่าวอ้างมีราคาที่บทที่ 9 สอนไว้แล้ว — ตัวคูณคำโกหกวัดว่า ภาพ แสดงผลใหญ่กว่าที่ ข้อมูล มีจริงกี่เท่า หัวข้อก็วัดแบบเดียวกันได้
ความต่างอยู่ตรงที่มันร้ายกว่า เพราะข้อเท็จจริงที่หัวข้อแบกอยู่ คือข้อที่ภาพแสดงไม่ได้ตั้งแต่แรก คนอ่านจึงไม่มีทางเอาภาพมาตรวจประโยคของคุณได้เลย
| หัวข้อ | ข้อมูลรองรับไหม | คนอ่านตรวจได้ไหม |
|---|---|---|
| หน่วย ค ใช้มากที่สุด 25 จาก 100 | รองรับ | ตรวจได้จากแท่ง |
| จ กับ ข ต่างกัน 1 จุด | รองรับ | ตรวจไม่ได้ ต้องเชื่อคุณ |
| หน่วย ค ทิ้งห่างหน่วยอื่นแบบขาดลอย | ไม่รองรับ ห่างอันดับสองแค่ 3 จุด | ตรวจไม่ได้ ต้องเชื่อคุณ |
แถวที่สองกับสามหน้าตาเหมือนกันในสายตาคนฟัง ทั้งคู่คือประโยคที่ต้องเชื่อ ต่างกันแค่ว่าแถวหนึ่งจริงและอีกแถวไม่จริง
หัวข้อคือที่ที่คุณใช้เครดิตของตัวเอง ไม่ใช่ที่ที่คุณใช้ข้อมูลของคนอื่น
และมีสถานการณ์ที่หัวข้อ ไม่ควร บอกข้อสรุปอยู่จริง สองแบบ
- หน้าที่เอาไว้เปิดหา เช่นตารางอ้างอิงหรือภาคผนวก ซึ่งงานของมันคือให้คนไปหยิบตัวเลขที่ต้องการ ไม่ใช่พาไปหาข้อสรุปเดียว
- ตอนที่คุณยังไม่มีข้อสรุป ซึ่งเกิดขึ้นจริงและไม่ใช่เรื่องน่าอาย · หัวข้อที่บอกชื่อเรื่องแล้วบอกตรง ๆ ว่ายังตอบไม่ได้ ดีกว่าหัวข้อที่แต่งข้อสรุปขึ้นมาให้ดูเป็นมืออาชีพ
LEVEL 4 · ระดับเทพ
รื้อสไลด์จริงมาประกอบใหม่#
สิบสามบทที่ผ่านมายื่นกลไกมาให้ทีละชิ้น บทนี้เอาทุกชิ้นมาประกอบพร้อมกันบนสไลด์แผ่นเดียว
กติกาของบทนี้มีข้อเดียวและมันเข้มงวดมาก — ทุกการตัดสินใจต้องชี้กลับไปได้ว่ามาจากบทไหน และต้องมีตัวเลขที่ขยับ ห้ามมีข้อไหนตอบว่า "เพราะมันดูดีกว่า"
ถ้าเล่มนี้ทำงานของมันสำเร็จ พออ่านจบบทนี้คุณจะรื้อสไลด์ของตัวเองด้วยวิธีเดียวกันได้ โดยไม่ต้องเปิดกลับมาดู
14.1 สไลด์ตั้งต้น
สไลด์นี้ไม่ได้ผิดสักตัวเลข ทุกชิ้นวงกลมมีมุมถูกต้องตามสัดส่วน ถ้าเอาไปให้ตรวจ มันผ่าน
และมันคือหน้าตาของสไลด์ที่พบได้จริงในห้องประชุมทุกที่ — ไม่ใช่เพราะใครขี้เกียจ แต่เพราะทุกชิ้นในนั้นคือค่าเริ่มต้นของเครื่องมือ หรือเป็นสิ่งที่ "เขาทำกันมาแบบนี้"
อาการที่มองเห็นด้วยตาเปล่ามีหกอย่าง และตอนนี้คุณเรียกชื่อมันได้ทุกอย่างแล้ว
| อาการ | บทที่อธิบาย |
|---|---|
| หัวข้อบอกแค่ชื่อเรื่อง คนอ่านต้องหาข้อสรุปเอง | 12 · 13 |
| ใช้มุมแบกค่าที่ต้องเทียบกัน | 4 · 5 |
| เรียงตามลำดับในตารางต้นทาง ไม่ได้เรียงตามค่า | 2 · 11 |
| คำอธิบายสัญลักษณ์วางแยกจากชิ้นที่มันอธิบาย | 2 · 11 |
| สีห้าสีทำหน้าที่ของป้ายชื่อ | 8 |
| เงา กรอบ และเส้นคั่นที่ไม่ตอบคำถามอะไรเลย | 7 |
⚠️ สังเกตว่าไม่มีข้อไหนในตารางนี้ที่พูดถึงความสวย และไม่มีข้อไหนที่แก้ได้ด้วยการเปลี่ยนชุดสีหรือเปลี่ยนฟอนต์ ทั้งหกข้อเกิดที่ขั้นเข้ารหัส ในสายพานของบทที่ 3 ทั้งหมด
14.2 วัดของเดิมก่อน แล้วค่อยแตะ
นิสัยที่ควรติดตัวจากบทนี้คือ วัดก่อนแก้ เพราะไม่งั้นตอนแก้เสร็จคุณจะไม่มีทางรู้ว่าดีขึ้นจริงหรือแค่รู้สึกว่าดีขึ้น
สคริปต์ข้างล่าง (rebuild.py) คือเครื่องวัดของบทนี้ เกือบทุกสูตรในนั้นยกมาจากบทก่อนหน้าตรง ๆ แค่เอามารวมไว้ที่เดียวแล้วชี้ไปที่สไลด์สองใบ · มีอย่างเดียวที่ต่อยอด คือบัญชีหมึกที่แยกเป็นสามช่อง แทนสองช่องของบทที่ 7 ซึ่งเป็นการลงมือทำตามเส้นแบ่งที่หัวข้อ 7.3 ขีดไว้แต่ยังไม่ได้ใช้
💡 กติกาการนับ ที่บทที่ 7 ใช้อยู่แล้วแต่ไม่เคยเขียนออกมา — คำอธิบายสัญลักษณ์นับแถวละหนึ่งชิ้น ทั้งที่วาดจริงเป็นสองรูป (สี่เหลี่ยมสีกับชื่อ) เพราะตาต้องอ่านมันคู่กันเสมอ แยกจากกันแล้วไม่มีความหมาย · ส่วนตัวเลขบนแท่งกับป้ายใต้แท่งนับแยกกัน เพราะเป็นข้อมูลคนละอย่าง อ่านทีละอันได้ · ถ้านับรูปที่วาดจริงแทน ใบตั้งต้นจะได้ 23 ไม่ใช่ 18 — ตัวเลขในบัญชีนี้จึงนับ "หน่วยที่สายตาต้องประมวลผล" ไม่ใช่ "จำนวนรูปทรงในไฟล์"
"""รื้อสไลด์แล้วประกอบใหม่ — ตัวเลขทุกตัวของบทนี้มาจากสคริปต์เดียวนี้"""
import math
DATA = [("ก", 22), ("ข", 18), ("ค", 25), ("ง", 16), ("จ", 19)]
K = 0.05
vals = [v for _, v in DATA]
hi, total = max(vals), sum(vals)
rank = sorted(DATA, key=lambda kv: -kv[1])
# --- 1. บัญชีหมึกสามช่อง — ต่อยอดจาก dataink.py ในบทที่ 7 ที่มีแค่สองช่อง ---
# บริบท = หน่วย ที่มา เส้นอ้างอิง ซึ่งบทที่ 7.3 ห้ามลบ · เปล่า = ไม่ตอบคำถามไหนเลย
BEFORE_INK = [
("ชิ้นวงกลม 5 ชิ้น", 5, "ข้อมูล"),
("คำอธิบายสัญลักษณ์ 5 บรรทัด", 5, "เปล่า"),
("วงแหวนเงาใต้วงกลม", 1, "เปล่า"),
("กรอบรอบภาพ 4 ด้าน", 4, "เปล่า"),
("เส้นคั่นใต้หัวข้อ", 1, "เปล่า"),
("หัวข้อที่บอกแค่ชื่อเรื่อง", 1, "บริบท"),
("บรรทัดหน่วยและที่มา", 1, "บริบท"),
]
AFTER_INK = [
("แท่ง 5 อัน", 5, "ข้อมูล"),
("ตัวเลขบนแท่ง", 5, "ข้อมูล"),
("ป้ายชื่อใต้แท่ง", 5, "ข้อมูล"),
("หัวข้อที่บอกข้อสรุป", 1, "ข้อมูล"),
("เส้นฐานเส้นเดียว", 1, "บริบท"),
("บรรทัดหน่วยและที่มา", 1, "บริบท"),
]
def ink(name, items):
kinds = {"ข้อมูล": 0, "บริบท": 0, "เปล่า": 0}
for _, n, kind in items:
kinds[kind] += n
t = sum(kinds.values())
print(f"=== บัญชีหมึกของ{name} ===")
for label, n, kind in items:
print(f" [{kind}] {label} = {n}")
print(f" รวม {t} ชิ้น · ข้อมูล {kinds['ข้อมูล']} · บริบท {kinds['บริบท']} "
f"· เปล่า {kinds['เปล่า']} · หมึกที่เป็นข้อมูล {kinds['ข้อมูล'] / t * 100:.1f}%\n")
return kinds, t
b_kind, b_total = ink("สไลด์ตั้งต้น", BEFORE_INK)
a_kind, a_total = ink("สไลด์ที่ประกอบใหม่", AFTER_INK)
print(f"จำนวนชิ้นรวม {b_total} -> {a_total} · หมึกเปล่า {b_kind['เปล่า']} -> {a_kind['เปล่า']}"
f" · หมึกที่เป็นข้อมูล {b_kind['ข้อมูล']} -> {a_kind['ข้อมูล']}")
print("ที่ว่างที่ได้จากการลบ ไม่ได้กลายเป็นที่ว่าง มันถูกเอาไปใส่ข้อมูลจนเต็ม\n")
# --- 2. งานโหมดไล่ทีละชิ้นที่สไลด์โยนไปให้คนอ่าน (บทที่ 2) ---
def serial_jobs(n, legend, sorted_by_value, values_printed):
jobs = []
if legend:
jobs.append(("จับคู่ชิ้นกับคำอธิบายสัญลักษณ์", n))
if not sorted_by_value:
jobs.append(("ไล่เทียบเองเพื่อหาอันดับ", n - 1))
if not values_printed:
jobs.append(("กะค่าจากภาพทีละชิ้น", n))
return jobs
print("=== งานโหมดไล่ทีละชิ้นที่คนอ่านต้องทำ ===")
for name, kw in (("สไลด์ตั้งต้น", dict(legend=True, sorted_by_value=False, values_printed=False)),
("สไลด์ที่ประกอบใหม่", dict(legend=False, sorted_by_value=True, values_printed=True))):
jobs = serial_jobs(len(DATA), **kw)
print(f" {name}: {sum(c for _, c in jobs)} ครั้ง")
for label, c in jobs:
print(f" {label} {c} ครั้ง")
if not jobs:
print(" ไม่มีเลย ทุกอย่างอ่านได้ในโหมดขนาน")
print()
# --- 2ก. สีทำหน้าที่อะไร และคนอ่านต้องจำอะไรบ้าง (บทที่ 2, 8) ---
print("=== หน้าที่ของสี ===")
for name, hues, job, must_remember in (
("สไลด์ตั้งต้น", 5, "แยกตัวตนของกลุ่ม", 5),
("สไลด์ที่ประกอบใหม่", 1, "เน้นข้อสรุปหนึ่งจุด", 0)):
print(f" {name}: ใช้ {hues} สี · สีทำหน้าที่{job} "
f"· คนอ่านต้องจำคู่สี-ชื่อ {must_remember} คู่")
print(" ทั้งสองใบไม่ได้เอาสีไปแบกปริมาณ ข้อผิดพลาดของบทที่ 8 จึงไม่ได้อยู่ที่สไลด์นี้")
print(" สิ่งที่สีทำผิดในใบตั้งต้นคือทำหน้าที่ของป้าย ทั้งที่ป้ายวางติดชิ้นได้เลย\n")
# --- 3. ช่องทางไหนแบกช่องว่างไหนไหว (สูตรจาก channels.py บทที่ 5) ---
def share_of_range(gap, channel):
return gap / hi if channel == "ความยาว" else gap / total
steps = [(rank[i], rank[i + 1]) for i in range(len(rank) - 1)]
tight = min(steps, key=lambda p: p[0][1] - p[1][1])
FACTS = [(f"อันดับ {i + 1} กับ {i + 2}", a, b) for i, (a, b) in enumerate(steps)]
FACTS.append(("หัวตารางกับท้ายตาราง", rank[0], rank[-1]))
print("=== ย้ายช่องทางจากมุมไปความยาว แล้วช่องว่างได้ส่วนแบ่งเท่าไหร่ ===")
homeless = {"มุม": [], "ความยาว": []}
for label, a, b in FACTS:
gap = a[1] - b[1]
cells = []
for ch in ("มุม", "ความยาว"):
s = share_of_range(gap, ch)
if s < K:
homeless[ch].append((a[0], b[0], gap))
cells.append(f"{ch} {s * 100:5.2f}% {'ผ่าน' if s >= K else 'ไม่ผ่าน'}")
print(f" {label} ({gap} จุด): " + " | ".join(cells))
print(f" ข้อที่ภาพแบกไม่ไหว: วงกลม {len(homeless['มุม'])} ข้อ · แท่ง {len(homeless['ความยาว'])} ข้อ\n")
# --- 4. ถ้าตัดฐานแกนของแท่ง จะเกิดอะไร (สูตรจาก liefactor.py บทที่ 9) ---
def lie_factor(a, b, floor):
real = abs(a - b) / min(a, b)
ha, hb = a - floor, b - floor
shown = abs(ha - hb) / min(ha, hb)
return real, shown, shown / real
A, B = tight[0][1], tight[1][1]
print(f"=== คู่ที่สูสีที่สุด ({A} กับ {B}) ถ้าตัดฐานแกนออก ===")
for floor in (0, 10, 15, 17):
real, shown, lf = lie_factor(A, B, floor)
print(f" ฐานแกน {floor:>3} -> ผลจริง {real * 100:6.2f}% "
f"-> ภาพแสดง {shown * 100:6.2f}% -> ตัวคูณคำโกหก {lf:.2f}")
print(" ศูนย์คือฐานเดียวที่ตัวคูณเท่ากับ 1 และแท่งอ่านค่าจากความยาว จึงไม่มีทางเลือกอื่น\n")
# --- 5. หัวข้อที่ประกอบจากข้อเท็จจริงที่แท่งแบกไม่ไหว (วิธีจาก headline.py บทที่ 13) ---
HEADLINE = " · ".join(
[f"หน่วย {rank[0][0]} ใช้มากที่สุด {rank[0][1]} จาก {total}"]
+ [f"{a} กับ {b} ต่างกัน {gap} จุด" for a, b, gap in homeless["ความยาว"]]
)
print("=== หัวข้อของสไลด์ที่ประกอบใหม่ ===")
print(f" {HEADLINE}")
print(f" แบกข้อเท็จจริง {1 + len(homeless['ความยาว'])} ข้อ · ยาว {len(HEADLINE)} ตัวอักษร\n")
# --- 6. เรขาคณิตของภาพทั้งสามในบทนี้ ---
print("=== เรขาคณิตของ FIG 14.1 (วงกลม รัศมี 72 เรียงตามลำดับในตาราง) ===")
start, R = -90.0, 72.0
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
print("\n=== เรขาคณิตของ FIG 14.2 (แท่งเรียงจากมาก ฐานที่ศูนย์ สูงสุด 110 px) ===")
BAR_MAX = 110.0
for k, v in rank:
print(f" {k}: {v:>3} -> สูง {v / hi * BAR_MAX:6.2f} px")
print("\n=== เรขาคณิตของ FIG 14.3 (จุดบนแกนร่วม ซูมช่วง 16-25 กว้าง 480 px) ===")
lo_v, hi_v, AXIS = min(vals), hi, 480.0
for k, v in rank:
print(f" {k}: {v:>3} -> x {(v - lo_v) / (hi_v - lo_v) * AXIS:6.2f} px")
gap_tight = A - B
print(f" ช่องว่าง {gap_tight} จุด บนแกนที่ซูมแล้ว = "
f"{gap_tight / (hi_v - lo_v) * 100:.2f}% ของช่วงที่วาด "
f"(บนแท่งฐานศูนย์ได้ {share_of_range(gap_tight, 'ความยาว') * 100:.2f}%)")ผลรันจริง
=== บัญชีหมึกของสไลด์ตั้งต้น ===
[ข้อมูล] ชิ้นวงกลม 5 ชิ้น = 5
[เปล่า] คำอธิบายสัญลักษณ์ 5 บรรทัด = 5
[เปล่า] วงแหวนเงาใต้วงกลม = 1
[เปล่า] กรอบรอบภาพ 4 ด้าน = 4
[เปล่า] เส้นคั่นใต้หัวข้อ = 1
[บริบท] หัวข้อที่บอกแค่ชื่อเรื่อง = 1
[บริบท] บรรทัดหน่วยและที่มา = 1
รวม 18 ชิ้น · ข้อมูล 5 · บริบท 2 · เปล่า 11 · หมึกที่เป็นข้อมูล 27.8%
=== บัญชีหมึกของสไลด์ที่ประกอบใหม่ ===
[ข้อมูล] แท่ง 5 อัน = 5
[ข้อมูล] ตัวเลขบนแท่ง = 5
[ข้อมูล] ป้ายชื่อใต้แท่ง = 5
[ข้อมูล] หัวข้อที่บอกข้อสรุป = 1
[บริบท] เส้นฐานเส้นเดียว = 1
[บริบท] บรรทัดหน่วยและที่มา = 1
รวม 18 ชิ้น · ข้อมูล 16 · บริบท 2 · เปล่า 0 · หมึกที่เป็นข้อมูล 88.9%
จำนวนชิ้นรวม 18 -> 18 · หมึกเปล่า 11 -> 0 · หมึกที่เป็นข้อมูล 5 -> 16
ที่ว่างที่ได้จากการลบ ไม่ได้กลายเป็นที่ว่าง มันถูกเอาไปใส่ข้อมูลจนเต็ม
=== งานโหมดไล่ทีละชิ้นที่คนอ่านต้องทำ ===
สไลด์ตั้งต้น: 14 ครั้ง
จับคู่ชิ้นกับคำอธิบายสัญลักษณ์ 5 ครั้ง
ไล่เทียบเองเพื่อหาอันดับ 4 ครั้ง
กะค่าจากภาพทีละชิ้น 5 ครั้ง
สไลด์ที่ประกอบใหม่: 0 ครั้ง
ไม่มีเลย ทุกอย่างอ่านได้ในโหมดขนาน
=== หน้าที่ของสี ===
สไลด์ตั้งต้น: ใช้ 5 สี · สีทำหน้าที่แยกตัวตนของกลุ่ม · คนอ่านต้องจำคู่สี-ชื่อ 5 คู่
สไลด์ที่ประกอบใหม่: ใช้ 1 สี · สีทำหน้าที่เน้นข้อสรุปหนึ่งจุด · คนอ่านต้องจำคู่สี-ชื่อ 0 คู่
ทั้งสองใบไม่ได้เอาสีไปแบกปริมาณ ข้อผิดพลาดของบทที่ 8 จึงไม่ได้อยู่ที่สไลด์นี้
สิ่งที่สีทำผิดในใบตั้งต้นคือทำหน้าที่ของป้าย ทั้งที่ป้ายวางติดชิ้นได้เลย
=== ย้ายช่องทางจากมุมไปความยาว แล้วช่องว่างได้ส่วนแบ่งเท่าไหร่ ===
อันดับ 1 กับ 2 (3 จุด): มุม 3.00% ไม่ผ่าน | ความยาว 12.00% ผ่าน
อันดับ 2 กับ 3 (3 จุด): มุม 3.00% ไม่ผ่าน | ความยาว 12.00% ผ่าน
อันดับ 3 กับ 4 (1 จุด): มุม 1.00% ไม่ผ่าน | ความยาว 4.00% ไม่ผ่าน
อันดับ 4 กับ 5 (2 จุด): มุม 2.00% ไม่ผ่าน | ความยาว 8.00% ผ่าน
หัวตารางกับท้ายตาราง (9 จุด): มุม 9.00% ผ่าน | ความยาว 36.00% ผ่าน
ข้อที่ภาพแบกไม่ไหว: วงกลม 4 ข้อ · แท่ง 1 ข้อ
=== คู่ที่สูสีที่สุด (19 กับ 18) ถ้าตัดฐานแกนออก ===
ฐานแกน 0 -> ผลจริง 5.56% -> ภาพแสดง 5.56% -> ตัวคูณคำโกหก 1.00
ฐานแกน 10 -> ผลจริง 5.56% -> ภาพแสดง 12.50% -> ตัวคูณคำโกหก 2.25
ฐานแกน 15 -> ผลจริง 5.56% -> ภาพแสดง 33.33% -> ตัวคูณคำโกหก 6.00
ฐานแกน 17 -> ผลจริง 5.56% -> ภาพแสดง 100.00% -> ตัวคูณคำโกหก 18.00
ศูนย์คือฐานเดียวที่ตัวคูณเท่ากับ 1 และแท่งอ่านค่าจากความยาว จึงไม่มีทางเลือกอื่น
=== หัวข้อของสไลด์ที่ประกอบใหม่ ===
หน่วย ค ใช้มากที่สุด 25 จาก 100 · จ กับ ข ต่างกัน 1 จุด
แบกข้อเท็จจริง 2 ข้อ · ยาว 55 ตัวอักษร
=== เรขาคณิตของ FIG 14.1 (วงกลม รัศมี 72 เรียงตามลำดับในตาราง) ===
ก: 22 -> 79.20 องศา | M 0 0 L 0.00 -72.00 A 72.0 72.0 0 0 1 70.72 -13.49 Z
ข: 18 -> 64.80 องศา | M 0 0 L 70.72 -13.49 A 72.0 72.0 0 0 1 42.32 58.25 Z
ค: 25 -> 90.00 องศา | M 0 0 L 42.32 58.25 A 72.0 72.0 0 0 1 -58.25 42.32 Z
ง: 16 -> 57.60 องศา | M 0 0 L -58.25 42.32 A 72.0 72.0 0 0 1 -66.94 -26.50 Z
จ: 19 -> 68.40 องศา | M 0 0 L -66.94 -26.50 A 72.0 72.0 0 0 1 -0.00 -72.00 Z
=== เรขาคณิตของ FIG 14.2 (แท่งเรียงจากมาก ฐานที่ศูนย์ สูงสุด 110 px) ===
ค: 25 -> สูง 110.00 px
ก: 22 -> สูง 96.80 px
จ: 19 -> สูง 83.60 px
ข: 18 -> สูง 79.20 px
ง: 16 -> สูง 70.40 px
=== เรขาคณิตของ FIG 14.3 (จุดบนแกนร่วม ซูมช่วง 16-25 กว้าง 480 px) ===
ค: 25 -> x 480.00 px
ก: 22 -> x 320.00 px
จ: 19 -> x 160.00 px
ข: 18 -> x 106.67 px
ง: 16 -> x 0.00 px
ช่องว่าง 1 จุด บนแกนที่ซูมแล้ว = 11.11% ของช่วงที่วาด (บนแท่งฐานศูนย์ได้ 4.00%)⚠️ ตัวเลข "14 ครั้ง" มาจาก serial_jobs ซึ่งเป็นแบบจำลองของงานที่คนอ่านต้องทำ ไม่ใช่ค่าที่จับเวลามาจากคนจริง หลักเดียวกับ threshold ในบทที่ 11 · สิ่งที่มันพิสูจน์ได้จริงคือ งานสามกองนั้นมีอยู่จริงและหายไปได้ทั้งกอง ไม่ใช่ว่าคนอ่านใช้เวลากี่วินาที
14.3 เจ็ดการตัดสินใจ
ทุกข้อข้างล่างมีโครงเดียวกันสามบรรทัด: คำถามที่ต้องตอบ · กลไกที่ตอบให้ · ตัวเลขที่ขยับหลังตอบ
หนึ่ง — ย้ายจากมุมไปความยาว คำถาม: ช่องทางที่ใช้อยู่แบกช่องว่างในชุดนี้ไหวไหม กลไก (บทที่ 4 · 5): มุมหารด้วยวงเต็ม ความยาวหารด้วยค่าสูงสุด ตัวหารต่างกันสี่เท่าตั้งแต่ต้น ตัวเลข: ข้อที่ภาพแสดงไม่ได้ลดจาก 4 ข้อ เหลือ 1 ข้อ · ช่องว่าง 3 จุดระหว่างอันดับหนึ่งกับสองเปลี่ยนจาก 3.00% เป็น 12.00% ซึ่งข้ามเกณฑ์พอดี
สอง — เรียงตามค่า คำถาม: ลำดับที่วางอยู่ตอนนี้บอกอะไรคนอ่าน กลไก (บทที่ 2 · 11): ลำดับในตารางต้นทางเป็นข้อมูลที่ไม่มีความหมายกับคำถามนี้ แต่ตาจะอ่านมันเป็นโครงสร้างอยู่ดี การเรียงคือการเข้ารหัสอันดับลงไปในตำแหน่ง ซึ่งไม่กินหมึกเลยสักหยด ตัวเลข: งานไล่เทียบเพื่อหาอันดับเอง 4 ครั้ง หายไปทั้งกอง
สาม — ย้ายป้ายไปติดแท่ง คำถาม: คนอ่านต้องเดินสายตากี่รอบกว่าจะรู้ว่าชิ้นไหนคือหน่วยไหน กลไก (บทที่ 2 · 11): การจับคู่ชิ้นกับคำอธิบายสัญลักษณ์คืองานโหมดไล่ทีละชิ้น และระยะห่างกำลังบอกว่าป้ายไม่ใช่พวกเดียวกับแท่ง ทั้งที่มันคือของชิ้นเดียวกัน ตัวเลข: การจับคู่ 5 ครั้ง หายไปทั้งกอง · หมึกเปล่าลดลง 5 ชิ้น
สี่ — ปลดสีออกจากหน้าที่ของป้าย คำถาม: สีกำลังทำงานอะไรอยู่บนสไลด์นี้ กลไก (บทที่ 8 · 2): สีห้าสีในใบตั้งต้นไม่ได้แบกปริมาณ ซึ่งถูกแล้ว แต่มันแบกตัวตนของกลุ่ม ผ่านคำอธิบายสัญลักษณ์ พอย้ายป้ายไปติดแท่ง สีก็ไม่เหลืองานให้ทำ และบทที่ 2 บอกว่ายิ่งจำนวนสีมาก การเด้งยิ่งอ่อนลงเรื่อย ๆ ตัวเลข: คู่สี-ชื่อที่คนอ่านต้องจำ 5 คู่ เหลือ 0 · สีที่ใช้ 5 เหลือ 1 และสีเดียวที่เหลือทำหน้าที่เน้นข้อสรุป
ห้า — ลบหมึกที่ไม่ตอบคำถามอะไรเลย คำถาม: ถ้าลบชิ้นนี้ออก มีคำถามไหนที่คนอ่านตอบไม่ได้อีกต่อไปหรือเปล่า กลไก (บทที่ 7): เงา กรอบ และเส้นคั่นตอบว่า "ไม่มี" ส่วนบรรทัดหน่วยและที่มาตอบว่า "มี" จึงอยู่ต่อ นี่คือเส้นแบ่งของหัวข้อ 7.3 ที่บทนี้เอามาใช้จริงเป็นครั้งแรก ตัวเลข: หมึกเปล่า 11 ชิ้น เหลือ 0 · สัดส่วนหมึกที่เป็นข้อมูลจาก 27.8% เป็น 88.9%
หก — ฐานแกนที่ศูนย์ และคำนวณให้เห็นว่าทำไม คำถาม: แกนควรเริ่มที่ไหน กลไก (บทที่ 6 · 9): แท่งเข้ารหัสด้วยความยาว ซึ่งอ่านค่าจากจุดเริ่มถึงจุดจบ ตัดฐานทิ้งเท่ากับโกหกเรื่องความยาว ตัวเลข: ถ้าตัดฐานที่ 15 คู่ที่ต่างกันจริง 5.56% จะถูกแสดงเป็น 33.33% คือ ตัวคูณคำโกหก 6.00 · ที่ฐาน 17 ตัวคูณพุ่งเป็น 18.00 · มีฐานเดียวที่ตัวคูณเท่ากับ 1 คือศูนย์
⚠️ ข้อนี้ไม่ได้แก้อาการไหนในตารางของหัวข้อ 14.1 เลย เพราะวงกลมไม่มีแกนให้ตัดตั้งแต่แรก มันเป็นการตัดสินใจที่เกิดขึ้นใหม่เพราะข้อที่หนึ่งย้ายมาใช้แท่ง — ทุกครั้งที่คุณเปลี่ยนช่องทาง คุณได้ทั้งความสามารถใหม่และภาระใหม่มาพร้อมกัน
เจ็ด — เขียนหัวข้อจากรายการที่เหลือ คำถาม: ภาพที่ได้ตอนนี้ยังแบกอะไรไม่ไหว กลไก (บทที่ 13 · 12): ข้อที่ตกเกณฑ์คือข้อที่ไม่ได้ถูกส่งไปถึงคนอ่านเลย ต้องเขียนบอก และข้อสรุปต้องขึ้นก่อนตามหลักพีระมิด ตัวเลข: เหลือ 1 ข้อ ที่แท่งแสดงไม่ได้ · หัวข้อจึงแบก 2 ข้อเท็จจริงและยาว 55 ตัวอักษร — เทียบกับ 127 ตัวอักษรถ้ายังใช้วงกลมอยู่
14.4 สไลด์ที่ประกอบใหม่
บรรทัดที่ควรอ่านซ้ำที่สุดของบทนี้อยู่ในผลรันของบัญชีหมึก
จำนวนชิ้นรวมเท่าเดิมเป๊ะ 18 ชิ้นทั้งสองใบ
เรื่องนี้สำคัญเพราะมันหักล้างความเข้าใจที่ว่า "ทำให้เรียบง่าย" แปลว่า "เอาของออก" หมึกเปล่า 11 ชิ้นหายไปจริง แต่ที่ว่างที่ได้มาไม่ได้กลายเป็นที่ว่าง มันถูกเอาไปใส่ตัวเลขบนแท่ง ป้ายใต้แท่ง และหัวข้อที่บอกข้อสรุป
สไลด์ที่ดีขึ้นไม่ได้มีของน้อยลง มันมีของเท่าเดิมที่ทำงานคนละอย่าง
บทที่ 7 เคยแสดงตัวเลข 33 ชิ้นเหลือ 16 ซึ่งดูเหมือนเรื่องของการลด บทนี้ทำให้เห็นอีกครึ่งที่บทที่ 7 ยังไม่ได้พูด — งบหมึกที่ปลดออกมาได้ มีที่ให้ไปต่อ
14.5 ทางที่ไม่ได้เลือก และเหตุผลของมัน
บทที่ 5 บอกว่าตำแหน่งบนแกนร่วมอยู่ขั้นหนึ่งของบันได ส่วนความยาวอยู่ขั้นสาม แล้วทำไมสไลด์ที่ประกอบใหม่ถึงยังใช้แท่ง
ภาพนี้คือทางเลือกที่แพ้ไปอย่างหวุดหวิด และเหตุผลที่มันน่าสนใจคือมันชนะในเรื่องที่วัดได้ชัด ๆ
ข้อดีของมันมาจากบทที่ 9 โดยตรง — จุดเข้ารหัสด้วยตำแหน่ง ไม่ใช่ความยาว แกนจึงซูมได้โดยไม่ผิดหลักการ พอซูมช่วงเหลือ 16 ถึง 25 ช่องว่าง 1 จุดที่เคยได้ส่วนแบ่ง 4.00% บนแท่งที่เริ่มจากศูนย์ ก็กลายเป็น 11.11% ของช่วงที่วาดทันที
⚠️ ตรงนี้ต้องระวังคำ — 11.11% เป็นข้อเท็จจริงทางเรขาคณิต ไม่ใช่คำสัญญาว่าตาจะแยกออก เกณฑ์ K ในบทที่ 6 ตั้งไว้สำหรับความยาวที่เทียบกับตัวมันเอง เอามาทาบกับตำแหน่งบนแกนที่ซูมแล้วตรง ๆ ไม่ได้ สิ่งที่พูดได้แน่คือช่องว่างได้พื้นที่บนหน้ามากขึ้นเกือบสามเท่า
แล้วทำไมถึงไม่เลือก คำตอบอยู่ที่คำถามของสไลด์ ไม่ใช่ที่บันได
สไลด์ใบนี้ตอบคำถามว่า "แต่ละหน่วยใช้สัดส่วนเท่าไหร่จาก 100" ซึ่งเป็นคำถามที่ต้องอ่านค่าจากศูนย์ พอซูมแกนเป็น 16 ถึง 25 คำถามนั้นก็ตอบไม่ได้อีกต่อไป — ภาพจะบอกได้แค่ว่าใครมากกว่าใคร
ถ้าคำถามเปลี่ยนเป็น "ใครมากกว่าใคร และห่างกันแค่ไหน" จุดบนแกนร่วมชนะทันที และนั่นคือสิ่งที่หัวข้อ 5.5 พูดไว้ตั้งแต่ต้น ว่าบันไดวัดงานเดียว ถ้างานเปลี่ยน ลำดับก็เปลี่ยน
มีอีกสองทางที่ไม่ได้เลือก และเหตุผลสั้นกว่ามาก
- ไม่ได้ลบเส้นฐานกับบรรทัดที่มา ทั้งที่บัญชีนับว่ามันไม่ใช่ข้อมูล เพราะบทที่ 7.3 ขีดเส้นไว้แล้วว่าหมึกบริบทคนละเรื่องกับหมึกเปล่า ลบแล้วมีคำถามที่ตอบไม่ได้
- ไม่ได้แยกเป็นภาพชุด ตามบทที่ 11.4 เพราะข้อมูลชุดนี้มีมิติเดียว ห้าค่า ไม่มีอะไรให้แยก เครื่องมือนั้นรอวันที่มีมิติที่สอง
14.6 สิ่งที่บทนี้พิสูจน์
ไล่ทั้งเจ็ดข้อมาแล้ว ลองอ่านตารางนี้ทีเดียว
| การตัดสินใจ | มาจากบทที่ | ตัวเลขที่ขยับ |
|---|---|---|
| ย้ายจากมุมไปความยาว | 4 · 5 | ข้อที่ภาพแบกไม่ไหว 4 เหลือ 1 |
| เรียงตามค่า | 2 · 11 | งานไล่เทียบหาอันดับ 4 ครั้ง เหลือ 0 |
| ย้ายป้ายไปติดแท่ง | 2 · 11 | งานจับคู่ 5 ครั้ง เหลือ 0 |
| ปลดสีออกจากหน้าที่ของป้าย | 8 · 2 | คู่สี-ชื่อที่ต้องจำ 5 เหลือ 0 |
| ลบหมึกที่ไม่ตอบคำถามอะไร | 7 | หมึกเปล่า 11 ชิ้น เหลือ 0 |
| ฐานแกนที่ศูนย์ | 6 · 9 | ตัวคูณคำโกหก 6.00 เหลือ 1.00 |
| เขียนหัวข้อจากรายการที่เหลือ | 13 · 12 | หัวข้อ 127 ตัวอักษร เหลือ 55 |
ไม่มีข้อไหนในเจ็ดข้อนี้ที่ตอบว่า "เพราะมันดูดีกว่า" และไม่มีข้อไหนที่ต้องอาศัยรสนิยมของใคร
นี่คือสิ่งที่เล่มนี้สัญญาไว้ตั้งแต่บทที่ 1 ว่าจะไม่ให้กฎสิบข้อกับคุณ เพราะกฎบอกไม่ได้ว่าตัวเองใช้ไม่ได้เมื่อไหร่ ส่วนตัวเลขในคอลัมน์ขวาบอกได้เสมอ — พอเจอชุดข้อมูลใหม่ คุณรันใหม่แล้วดูว่าข้อไหนยังตก
ชุดเครื่องมือที่เอาไปใช้ต่อได้เอง#
บทที่แล้วรื้อสไลด์หนึ่งใบด้วยกลไกทั้งเล่ม บทนี้ทำสิ่งเดียว — บีบวิธีนั้นให้เล็กพอที่จะพกติดตัวไปใช้กับสไลด์ใบต่อไปของคุณเอง
และเล่มนี้จะจบด้วยสิ่งที่มันสัญญาไว้ตั้งแต่หน้าแรก คือไม่ให้กฎ สิ่งที่คุณจะได้จากบทนี้เป็น คำถาม ไม่ใช่คำสั่ง เพราะคำถามใช้ได้กับสถานการณ์ที่เล่มนี้ไม่เคยเห็น ส่วนกฎใช้ไม่ได้
15.1 เจ็ดคำถาม ไม่ใช่เจ็ดกฎ
ทุกข้อในตารางนี้ตอบด้วยตัวเลขได้ ไม่มีข้อไหนที่ต้องตอบด้วยความรู้สึก และช่องขวาสุดบอกว่าถ้าตกแล้วต้องกลับไปทำอะไร ไม่ใช่แค่บอกว่าตก
| คำถาม | กลไกอยู่บทที่ | ถ้าตก ทำอะไรต่อ |
|---|---|---|
| ช่องว่างที่แคบที่สุดได้ส่วนแบ่งเท่าไหร่ของช่วงที่ช่องทางมีให้ | 4 · 5 | ขึ้นบันไดหนึ่งขั้น · ถ้าขั้นบนสุดยังตก แปลว่าปัญหาไม่ใช่ชนิดกราฟ ให้เปลี่ยนสิ่งที่วัดหรือพิมพ์ตัวเลขไปเลย |
| ฐานแกนบิดสัดส่วนไปกี่เท่า | 6 · 9 | ความยาวต้องเริ่มจากศูนย์ · ตำแหน่งซูมได้แต่ต้องทำเครื่องหมายบอก |
| สีกำลังแบกอะไรอยู่ | 8 | แบกปริมาณ ให้เหลือสีเดียวไล่ความสว่าง · แบกกลุ่ม ให้ย้ายเป็นป้ายติดชิ้น |
| คนอ่านต้องไล่ทีละชิ้นกี่ครั้ง | 2 · 11 | เรียงตามค่า · ติดป้าย · พิมพ์ค่า จนกว่าจะเหลือศูนย์ |
| หมึกที่ไม่ตอบคำถามไหนเลยเหลือกี่ชิ้น | 7 | ถามทีละชิ้นว่าลบแล้วมีคำถามไหนที่ตอบไม่ได้อีกต่อไปหรือเปล่า |
| ชุดที่แสดงครบขอบเขตไหม ทับกันไหม | 12 | ทับกันแปลว่าใช้เกณฑ์แบ่งปนกัน ให้กลับไปแก้ที่เกณฑ์ อย่าย้ายรายการ |
| หัวข้อแบกข้อที่ภาพแบกไม่ไหวครบไหม | 13 | เขียนข้อที่ตกลงไปในหัวข้อ · เกลาถ้อยคำได้ ห้ามเติมข้อเท็จจริง |
สังเกตว่าไม่มีคำว่า "อย่า" อยู่ในตารางเลยสักช่อง เพราะทุกช่องเป็นการวัดแล้วตัดสิน ไม่ใช่การห้ามล่วงหน้า
15.2 เช็กลิสต์ในรูปของโปรแกรม
ตารางข้างบนใช้ด้วยมือได้ แต่มันจะไม่ถูกใช้จริงถ้าต้องนั่งไล่เอง สคริปต์ข้างล่าง (audit.py) จึงเอาทั้งเจ็ดข้อมาไว้ในฟังก์ชันเดียว แล้วรับสไลด์เป็น dict หนึ่งก้อน
เกือบทุกสูตรในไฟล์นี้ยกมาจากบทก่อนหน้าตรง ๆ · ที่เพิ่มใหม่มีสามอย่าง คืออันดับบันไดของบทที่ 5 ในรูปตัวเลข · ทางออกเมื่อเรขาคณิตให้ส่วนแบ่งไม่ได้ · และการตรวจว่าชุดที่แสดงรวมได้เท่าขอบเขตไหม
และมันตอบเป็น สามสถานะ ไม่ใช่สองอย่างที่เช็กลิสต์ทั่วไปทำ — ผ่าน · ตก · ข้าม · สถานะที่สามสำคัญที่สุด เพราะบางคำถามไม่เกี่ยวกับสไลด์ใบนั้นเลย (วงกลมไม่มีฐานแกนให้ตัด) และบางคำถามเครื่องมือตอบไม่ได้จริง ๆ (ความชันกับความเข้มไม่มีสูตรส่วนแบ่งของช่วง) · การเอาสองกรณีนี้ไปนับเป็น "ผ่าน" คือการโกหกตัวเอง
"""เช็กลิสต์เจ็ดข้อในรูปของโปรแกรม — คัดลอกไฟล์นี้ไปใช้กับสไลด์ของคุณเองได้เลย"""
K = 0.05
LADDER = {"ตำแหน่ง": 1, "ความยาว": 3, "ความชัน": 4, "มุม": 5, "พื้นที่": 6, "ความเข้ม": 7}
# คำถามเจ็ดข้อ กับบทที่กลไกของมันอยู่ — ลำดับนี้ตรงกับลำดับที่ audit() ตรวจ
CHECKS = [
("ช่องทางแบกช่องว่างที่แคบที่สุดไหวไหม", "4-5"),
("ฐานแกนบิดสัดส่วนหรือเปล่า", "6, 9"),
("สีกำลังแบกปริมาณอยู่ไหม", "8"),
("คนอ่านต้องไล่ทีละชิ้นกี่ครั้ง", "2, 11"),
("หมึกที่ไม่ตอบคำถามไหนเลยเหลือกี่ชิ้น", "7"),
("ชุดที่แสดงครบขอบเขตไหม", "12"),
("หัวข้อแบกข้อที่ภาพแบกไม่ไหวครบไหม", "13"),
]
def share_of_range(gap, vals, channel):
"""ช่องว่าง gap ได้ส่วนแบ่งเท่าไหร่ของช่วงที่ช่องทางมีให้ — None = เรขาคณิตตอบไม่ได้"""
hi, total = max(vals), sum(vals)
if channel in ("ตำแหน่ง", "ความยาว"):
return gap / hi
if channel == "มุม":
return gap / total
if channel == "พื้นที่":
return (hi ** 0.5 - (hi - gap) ** 0.5) / hi ** 0.5
return None
def lie_factor(a, b, floor):
real = abs(a - b) / min(a, b)
ha, hb = a - floor, b - floor
return abs(ha - hb) / min(ha, hb) / real
def audit(name, s):
"""ตรวจสไลด์หนึ่งใบ — สถานะต่อข้อมีสามแบบ: True ผ่าน · False ตก · None ตอบไม่ได้"""
vals = [v for _, v in s["data"]]
rank = sorted(s["data"], key=lambda kv: -kv[1])
steps = [(rank[i], rank[i + 1]) for i in range(len(rank) - 1)]
tight = min(steps, key=lambda p: p[0][1] - p[1][1])
big, small = tight[0][1], tight[1][1]
gap = big - small
rows = []
# 1 · ช่องทาง (บทที่ 4-5)
rung = LADDER[s["channel"]]
share = share_of_range(gap, vals, s["channel"])
if share is None:
rows.append((None, f"ช่องทาง {s['channel']} = ขั้น {rung} ของบันได "
f"· เรขาคณิตให้ส่วนแบ่งของช่วงไม่ได้ ต้องตัดสินด้วยมือ"))
else:
rows.append((share >= K, f"ช่องว่างแคบสุด {gap} จุด ได้ {share * 100:.2f}% ของช่วง "
f"(ช่องทาง {s['channel']} = ขั้น {rung} ของบันได)"))
# 2 · ฐานแกน (บทที่ 6, 9) — เกี่ยวเฉพาะช่องทางที่อ่านค่าจากความยาว
floor = s.get("axis_floor")
if s["channel"] not in ("ความยาว", "พื้นที่"):
rows.append((None, f"ช่องทาง {s['channel']} ไม่มีฐานแกนให้ตัด ข้อนี้ไม่เกี่ยว"))
elif floor is None:
rows.append((None, "ช่องทางนี้อ่านค่าจากความยาว แต่ spec ไม่ได้บอกฐานแกน จึงตัดสินไม่ได้"))
elif floor >= small:
rows.append((False, f"ฐานแกนที่ {floor} ตัดจนแท่งที่สั้นกว่าเหลือ {small - floor} "
f"· สัดส่วนหายไปทั้งหมด ตัวคูณคำนวณไม่ได้ด้วยซ้ำ"))
else:
lf = lie_factor(big, small, floor)
rows.append((abs(lf - 1) < 0.01, f"ฐานแกนที่ {floor} -> ตัวคูณคำโกหก {lf:.2f}"))
# 3 · สี (บทที่ 8) — ข้อนี้อ่านจาก spec ล้วน สคริปต์ไม่ได้เห็นสีจริง
rows.append((s["color_carries"] != "ปริมาณ",
f"spec บอกว่าสีทำหน้าที่ {s['color_carries']} ด้วย {s['hues']} สี"))
# 4 · งานโหมดไล่ทีละชิ้น (บทที่ 2, 11)
n = len(s["data"])
serial = (n if s["legend"] else 0) + (0 if s["sorted_by_value"] else n - 1) \
+ (0 if s["values_printed"] else n)
rows.append((serial == 0, f"คนอ่านต้องไล่ทีละชิ้น {serial} ครั้ง"))
# 5 · หมึกเปล่า (บทที่ 7) — อ่านจาก spec ล้วนเหมือนกัน ต้องนับจากภาพจริงมาก่อน
ink = s["ink"]
rows.append((ink["เปล่า"] == 0,
f"spec บอกว่าหมึกเปล่า {ink['เปล่า']} ชิ้น จาก {sum(ink.values())} "
f"· หมึกที่เป็นข้อมูล {ink['ข้อมูล'] / sum(ink.values()) * 100:.1f}%"))
# 6 · ความครบของชุด (บทที่ 12)
names = [k for k, _ in s["data"]]
complete = sum(vals) == s["universe"] and len(set(names)) == len(names)
rows.append((complete, f"{len(names)} กลุ่ม รวมได้ {sum(vals)} จากขอบเขต {s['universe']}"))
# 7 · หัวข้อ (บทที่ 13) — เทียบรายการที่คำนวณได้ กับรายการที่ spec ประกาศว่าหัวข้อพูดถึง
# ช่องทางที่เรขาคณิตวัดไม่ได้ (or 0) ถือว่าภาพแบกไม่ไหวทุกข้อไว้ก่อน จนกว่าจะพิสูจน์ว่าไหว
need = [(a[0], b[0], a[1] - b[1]) for a, b in steps + [(rank[0], rank[-1])]
if (share_of_range(a[1] - b[1], vals, s["channel"]) or 0) < K]
missing = [f for f in need if f not in s["headline_facts"]]
rows.append((not missing,
f"ภาพแบกไม่ไหว {len(need)} ข้อ "
f"· spec บอกว่าหัวข้อพูดถึง {len(need) - len(missing)} ข้อ"))
mark = {True: "ผ่าน", False: "ตก ", None: "ข้าม"}
tally = {v: sum(1 for ok, _ in rows if ok is v) for v in (True, False, None)}
print(f"=== {name} — ตก {tally[False]} · ผ่าน {tally[True]} · ข้าม {tally[None]} "
f"จาก {len(rows)} ข้อ ===")
for i, ((ok, detail), (question, src)) in enumerate(zip(rows, CHECKS), start=1):
print(f" {i}. [{mark[ok]}] {question} (บทที่ {src})")
print(f" {detail}")
print()
return tally[False]
DATA = [("ก", 22), ("ข", 18), ("ค", 25), ("ง", 16), ("จ", 19)]
SLIDE_BEFORE = dict(
data=DATA, universe=100, channel="มุม", axis_floor=None,
legend=True, sorted_by_value=False, values_printed=False,
hues=5, color_carries="ตัวตนของกลุ่ม",
ink={"ข้อมูล": 5, "บริบท": 2, "เปล่า": 11},
headline_facts=[],
)
SLIDE_AFTER = dict(
data=DATA, universe=100, channel="ความยาว", axis_floor=0,
legend=False, sorted_by_value=True, values_printed=True,
hues=1, color_carries="เน้นข้อสรุป",
ink={"ข้อมูล": 16, "บริบท": 2, "เปล่า": 0},
headline_facts=[("จ", "ข", 1)],
)
before = audit("สไลด์ตั้งต้นของบทที่ 14", SLIDE_BEFORE)
after = audit("สไลด์ที่ประกอบใหม่ในบทที่ 14", SLIDE_AFTER)
print(f"ข้อที่ตก {before} -> {after} โดยตัวเลขต้นทางไม่ขยับเลยสักตัว")
print("ทุกข้อที่ตกมีชื่อบทกำกับ จึงย้อนกลับไปอ่านกลไกที่มันมาได้เสมอ")
# --- เคสที่เครื่องมือต้องไม่ตาย: ช่องทางที่เรขาคณิตวัดไม่ได้ กับฐานแกนที่ตัดเกิน ---
print("\n=== เอาไปใช้กับสไลด์แบบอื่นแล้วมันตอบว่าอะไร ===")
audit("ถ้าเข้ารหัสด้วยความเข้มบน heatmap", {**SLIDE_AFTER, "channel": "ความเข้ม"})
audit("ถ้าเผลอตัดฐานแกนไว้ที่ 18", {**SLIDE_AFTER, "axis_floor": 18})
print("=== แผนที่ของ FIG 15.1: บทไหนอยู่ตรงไหน ===")
for i, (question, src) in enumerate(CHECKS, start=1):
print(f" ข้อ {i} <- บทที่ {src}: {question}")
print(" กรอบความคิด (ไม่ใช่ข้อตรวจ) <- บทที่ 1, 3, 10")ผลรันจริง
=== สไลด์ตั้งต้นของบทที่ 14 — ตก 4 · ผ่าน 2 · ข้าม 1 จาก 7 ข้อ ===
1. [ตก ] ช่องทางแบกช่องว่างที่แคบที่สุดไหวไหม (บทที่ 4-5)
ช่องว่างแคบสุด 1 จุด ได้ 1.00% ของช่วง (ช่องทาง มุม = ขั้น 5 ของบันได)
2. [ข้าม] ฐานแกนบิดสัดส่วนหรือเปล่า (บทที่ 6, 9)
ช่องทาง มุม ไม่มีฐานแกนให้ตัด ข้อนี้ไม่เกี่ยว
3. [ผ่าน] สีกำลังแบกปริมาณอยู่ไหม (บทที่ 8)
spec บอกว่าสีทำหน้าที่ ตัวตนของกลุ่ม ด้วย 5 สี
4. [ตก ] คนอ่านต้องไล่ทีละชิ้นกี่ครั้ง (บทที่ 2, 11)
คนอ่านต้องไล่ทีละชิ้น 14 ครั้ง
5. [ตก ] หมึกที่ไม่ตอบคำถามไหนเลยเหลือกี่ชิ้น (บทที่ 7)
spec บอกว่าหมึกเปล่า 11 ชิ้น จาก 18 · หมึกที่เป็นข้อมูล 27.8%
6. [ผ่าน] ชุดที่แสดงครบขอบเขตไหม (บทที่ 12)
5 กลุ่ม รวมได้ 100 จากขอบเขต 100
7. [ตก ] หัวข้อแบกข้อที่ภาพแบกไม่ไหวครบไหม (บทที่ 13)
ภาพแบกไม่ไหว 4 ข้อ · spec บอกว่าหัวข้อพูดถึง 0 ข้อ
=== สไลด์ที่ประกอบใหม่ในบทที่ 14 — ตก 1 · ผ่าน 6 · ข้าม 0 จาก 7 ข้อ ===
1. [ตก ] ช่องทางแบกช่องว่างที่แคบที่สุดไหวไหม (บทที่ 4-5)
ช่องว่างแคบสุด 1 จุด ได้ 4.00% ของช่วง (ช่องทาง ความยาว = ขั้น 3 ของบันได)
2. [ผ่าน] ฐานแกนบิดสัดส่วนหรือเปล่า (บทที่ 6, 9)
ฐานแกนที่ 0 -> ตัวคูณคำโกหก 1.00
3. [ผ่าน] สีกำลังแบกปริมาณอยู่ไหม (บทที่ 8)
spec บอกว่าสีทำหน้าที่ เน้นข้อสรุป ด้วย 1 สี
4. [ผ่าน] คนอ่านต้องไล่ทีละชิ้นกี่ครั้ง (บทที่ 2, 11)
คนอ่านต้องไล่ทีละชิ้น 0 ครั้ง
5. [ผ่าน] หมึกที่ไม่ตอบคำถามไหนเลยเหลือกี่ชิ้น (บทที่ 7)
spec บอกว่าหมึกเปล่า 0 ชิ้น จาก 18 · หมึกที่เป็นข้อมูล 88.9%
6. [ผ่าน] ชุดที่แสดงครบขอบเขตไหม (บทที่ 12)
5 กลุ่ม รวมได้ 100 จากขอบเขต 100
7. [ผ่าน] หัวข้อแบกข้อที่ภาพแบกไม่ไหวครบไหม (บทที่ 13)
ภาพแบกไม่ไหว 1 ข้อ · spec บอกว่าหัวข้อพูดถึง 1 ข้อ
ข้อที่ตก 4 -> 1 โดยตัวเลขต้นทางไม่ขยับเลยสักตัว
ทุกข้อที่ตกมีชื่อบทกำกับ จึงย้อนกลับไปอ่านกลไกที่มันมาได้เสมอ
=== เอาไปใช้กับสไลด์แบบอื่นแล้วมันตอบว่าอะไร ===
=== ถ้าเข้ารหัสด้วยความเข้มบน heatmap — ตก 1 · ผ่าน 4 · ข้าม 2 จาก 7 ข้อ ===
1. [ข้าม] ช่องทางแบกช่องว่างที่แคบที่สุดไหวไหม (บทที่ 4-5)
ช่องทาง ความเข้ม = ขั้น 7 ของบันได · เรขาคณิตให้ส่วนแบ่งของช่วงไม่ได้ ต้องตัดสินด้วยมือ
2. [ข้าม] ฐานแกนบิดสัดส่วนหรือเปล่า (บทที่ 6, 9)
ช่องทาง ความเข้ม ไม่มีฐานแกนให้ตัด ข้อนี้ไม่เกี่ยว
3. [ผ่าน] สีกำลังแบกปริมาณอยู่ไหม (บทที่ 8)
spec บอกว่าสีทำหน้าที่ เน้นข้อสรุป ด้วย 1 สี
4. [ผ่าน] คนอ่านต้องไล่ทีละชิ้นกี่ครั้ง (บทที่ 2, 11)
คนอ่านต้องไล่ทีละชิ้น 0 ครั้ง
5. [ผ่าน] หมึกที่ไม่ตอบคำถามไหนเลยเหลือกี่ชิ้น (บทที่ 7)
spec บอกว่าหมึกเปล่า 0 ชิ้น จาก 18 · หมึกที่เป็นข้อมูล 88.9%
6. [ผ่าน] ชุดที่แสดงครบขอบเขตไหม (บทที่ 12)
5 กลุ่ม รวมได้ 100 จากขอบเขต 100
7. [ตก ] หัวข้อแบกข้อที่ภาพแบกไม่ไหวครบไหม (บทที่ 13)
ภาพแบกไม่ไหว 5 ข้อ · spec บอกว่าหัวข้อพูดถึง 1 ข้อ
=== ถ้าเผลอตัดฐานแกนไว้ที่ 18 — ตก 2 · ผ่าน 5 · ข้าม 0 จาก 7 ข้อ ===
1. [ตก ] ช่องทางแบกช่องว่างที่แคบที่สุดไหวไหม (บทที่ 4-5)
ช่องว่างแคบสุด 1 จุด ได้ 4.00% ของช่วง (ช่องทาง ความยาว = ขั้น 3 ของบันได)
2. [ตก ] ฐานแกนบิดสัดส่วนหรือเปล่า (บทที่ 6, 9)
ฐานแกนที่ 18 ตัดจนแท่งที่สั้นกว่าเหลือ 0 · สัดส่วนหายไปทั้งหมด ตัวคูณคำนวณไม่ได้ด้วยซ้ำ
3. [ผ่าน] สีกำลังแบกปริมาณอยู่ไหม (บทที่ 8)
spec บอกว่าสีทำหน้าที่ เน้นข้อสรุป ด้วย 1 สี
4. [ผ่าน] คนอ่านต้องไล่ทีละชิ้นกี่ครั้ง (บทที่ 2, 11)
คนอ่านต้องไล่ทีละชิ้น 0 ครั้ง
5. [ผ่าน] หมึกที่ไม่ตอบคำถามไหนเลยเหลือกี่ชิ้น (บทที่ 7)
spec บอกว่าหมึกเปล่า 0 ชิ้น จาก 18 · หมึกที่เป็นข้อมูล 88.9%
6. [ผ่าน] ชุดที่แสดงครบขอบเขตไหม (บทที่ 12)
5 กลุ่ม รวมได้ 100 จากขอบเขต 100
7. [ผ่าน] หัวข้อแบกข้อที่ภาพแบกไม่ไหวครบไหม (บทที่ 13)
ภาพแบกไม่ไหว 1 ข้อ · spec บอกว่าหัวข้อพูดถึง 1 ข้อ
=== แผนที่ของ FIG 15.1: บทไหนอยู่ตรงไหน ===
ข้อ 1 <- บทที่ 4-5: ช่องทางแบกช่องว่างที่แคบที่สุดไหวไหม
ข้อ 2 <- บทที่ 6, 9: ฐานแกนบิดสัดส่วนหรือเปล่า
ข้อ 3 <- บทที่ 8: สีกำลังแบกปริมาณอยู่ไหม
ข้อ 4 <- บทที่ 2, 11: คนอ่านต้องไล่ทีละชิ้นกี่ครั้ง
ข้อ 5 <- บทที่ 7: หมึกที่ไม่ตอบคำถามไหนเลยเหลือกี่ชิ้น
ข้อ 6 <- บทที่ 12: ชุดที่แสดงครบขอบเขตไหม
ข้อ 7 <- บทที่ 13: หัวข้อแบกข้อที่ภาพแบกไม่ไหวครบไหม
กรอบความคิด (ไม่ใช่ข้อตรวจ) <- บทที่ 1, 3, 10ผลรันนี้มีสามจุดที่ควรอ่านช้า ๆ
จุดแรก สไลด์ที่ประกอบใหม่ ยังตกข้อที่หนึ่งอยู่ ช่องว่าง 1 จุดได้ 4.00% ซึ่งยังไม่ถึงเกณฑ์ นี่ไม่ใช่ความล้มเหลวของบทที่ 14 แต่มันคือสิ่งที่หัวข้อ 5.4 บอกไว้ตั้งแต่แรกว่า บางชุดข้อมูลไม่มีช่องทางไหนแสดงได้ และทางออกไม่ใช่การไล่เปลี่ยนชนิดกราฟ · ข้อที่เจ็ดจึงผ่าน เพราะข้อที่ตกถูกย้ายไปอยู่ในหัวข้อเรียบร้อยแล้ว เช็กลิสต์ที่ดีไม่ได้บังคับให้ทุกข้อผ่าน มันบังคับให้ทุกข้อที่ตกมีที่ไป
จุดที่สอง ข้อที่สามผ่านทั้งสองใบ เพราะสไลด์นี้ไม่ได้เอาสีไปแบกตัวเลขตั้งแต่ต้น ซึ่งเป็นเรื่องปกติและควรเป็นแบบนั้น รายการตรวจที่ทุกข้อตกเสมอไม่ใช่รายการตรวจ มันคือรายการข้อกล่าวหา
จุดที่สาม สองบล็อกท้ายคือการเอาเครื่องมือไปใช้กับสไลด์ที่ไม่ใช่สองใบนี้ — heatmap ที่เข้ารหัสด้วยความเข้ม กับสไลด์ที่เผลอตัดฐานแกนไว้ที่ 18 · ทั้งสองเคสมันตอบ ไม่ได้ตาย และคำตอบก็ใช้ได้จริง: เคสแรกบอกว่าเรขาคณิตวัดไม่ได้ ต้องตัดสินด้วยมือ · เคสที่สองบอกว่าแท่งที่สั้นกว่าเหลือศูนย์ ซึ่งพังยิ่งกว่าตัวคูณคำโกหกสูง ๆ เสียอีก · เครื่องมือที่ตายเมื่อเจอของนอกตัวอย่าง คือเครื่องมือที่ใช้ได้แค่กับตัวอย่าง
15.3 สี่อย่างที่สคริปต์นี้ตรวจไม่ได้
เครื่องมือที่ให้ตัวเลขทุกครั้งมีอันตรายในตัวมันเอง คือมันชวนให้เชื่อว่าอะไรที่มันไม่ได้พูดถึงแปลว่าไม่มีปัญหา สี่ข้อข้างล่างคือสิ่งที่มันมองไม่เห็น และทุกข้อร้ายแรงกว่าทุกข้อในเช็กลิสต์
ศูนย์ มันไม่เคยเห็นสไลด์ของคุณ ข้อนี้ต้องมาก่อนทุกข้อ เพราะมันเปลี่ยนวิธีอ่านผลรันทั้งหมด — audit() อ่าน dict ที่คุณพิมพ์เอง ไม่ได้อ่านภาพ · ข้อที่สามกับข้อที่ห้าตัดสินจาก color_carries กับ ink ล้วน ๆ และข้อที่เจ็ดก็เทียบกับ headline_facts ที่คุณประกาศ ไม่ได้อ่านสตริงหัวข้อจริง (สังเกตคำว่า "spec บอกว่า" ในผลรัน — มันเตือนตรงนั้น) · ถ้าคุณกรอกว่าหมึกเปล่าเป็น 0 มันก็เชื่อ · ด่านเดียวคือต้องนับจากภาพจริงก่อนกรอก ซึ่งเป็นงานของคุณ ไม่ใช่ของสคริปต์
⚠️ นี่คือกลไกเดียวกับตัวคูณคำโกหกของหัวข้อในบทที่ 13.4 เป๊ะ ๆ — ตัวเลขที่ดูน่าเชื่อที่สุด คือตัวเลขที่คนอ่านตรวจย้อนไม่ได้ และคราวนี้คนอ่านคือคุณเอง
หนึ่ง ข้อมูลถูกหรือเปล่า สคริปต์รับ DATA มาแล้วเชื่อทันที ถ้าตัวเลขผิดตั้งแต่ต้น มันจะให้กราฟที่ผ่านครบเจ็ดข้อและบอกเรื่องที่ไม่จริงได้อย่างสวยงาม · ด่านเดียวที่มีคือนิสัยจากบทที่ 10 — วาดให้ตัวเองดูก่อนสรุปเสมอ ซึ่งเป็นนิสัย ไม่ใช่ข้อตรวจ จึงไม่ได้อยู่ในรายการเจ็ดข้อ
สอง คำถามใช่หรือเปล่า ทุกข้อในเช็กลิสต์วัดว่าภาพตอบคำถามที่ตั้งไว้ได้ดีแค่ไหน แต่ไม่มีข้อไหนถามว่าคำถามนั้นถูกต้องไหม · นี่คือบทเรียนของหัวข้อ 14.5 ที่ขยายออกไป — จุดบนแกนร่วมแพ้แท่งเพราะคำถามของสไลด์ ไม่ใช่เพราะอันดับบนบันได ถ้าตั้งคำถามผิด กราฟที่ผ่านเจ็ดข้อก็ยังตอบผิดอยู่ดี
สาม คนฟังเป็นใคร เกณฑ์ K ไม่รู้ว่าคนดูนั่งห่างจากจอกี่เมตร ไม่รู้ว่าเขาเคยเห็นข้อมูลชุดนี้มาก่อนไหม และไม่รู้ว่าเขากำลังจะตัดสินใจเรื่องอะไร · เล่มนี้ให้กลไกของสายตา ซึ่งเหมือนกันทุกคน แต่บริบทไม่เหมือนกันสักคน
เครื่องมือที่ดีคือเครื่องมือที่บอกได้ว่าตัวเองไม่รู้อะไร
15.4 ทั้งเล่มในภาพเดียว
ภาพนี้คือสายพานของบทที่ 3 ที่คุณเห็นตั้งแต่ต้นเล่ม แค่แขวนทุกบทลงไปตรงขั้นที่มันทำงานอยู่จริง
พอวางแบบนี้แล้ว โครงของทั้งเล่มจะเห็นได้ในบรรทัดเดียว — บทส่วนใหญ่อยู่ที่ขั้นเข้ารหัส เพราะนั่นคือขั้นเดียวที่คุณคุมได้ ส่วนบทที่อยู่ตรงขั้นถอดรหัสไม่ได้สอนให้คุณทำอะไร มันสอนว่าคุณกำลังส่งของเข้าไปในเครื่องแบบไหน
สองปลายของสายพานก็บอกอะไรเหมือนกัน — บทที่ 10 ยืนอยู่ก่อนที่ข้อมูลจะขึ้นสายพานด้วยซ้ำ เพราะมันตรวจตัวเลขไม่ใช่ตรวจภาพ ส่วนบทที่ 1 อยู่ปลายสุด เพราะสไลด์ที่ต้องยืนอธิบายคืออาการที่โผล่ออกมาเมื่อขั้นใดขั้นหนึ่งก่อนหน้าล้มเหลว
และมีสามบทที่ไม่ได้แขวนอยู่บนสายพานเลย คือบทที่ 3 ซึ่งเป็นตัวสายพานเอง บทที่ 14 ที่ประกอบทั้งสายพานลงบนสไลด์เดียว และบทที่ 15 ที่คุณกำลังอ่านอยู่ ซึ่งเป็นเช็กลิสต์ที่เดินตามสายพานทีละขั้น
15.5 คุณกลายเป็นใคร
หน้าแรกของเล่มนี้สัญญาไว้ว่าอ่านจบแล้วคุณจะ มองกราฟตรงหน้าออกเองว่ามันจะถูกอ่านผิดตรงไหน และแก้ได้โดยไม่ต้องถามใคร
วิธีตรวจว่าสัญญานั้นเกิดขึ้นจริงไหม ไม่ใช่การถามว่าคุณจำอะไรได้บ้าง แต่คือการดูว่าคำถามในหัวคุณเปลี่ยนไปหรือยัง กลับไปดูตารางในหัวข้อ 3.3 อีกครั้ง
ถ้าตอนนี้คุณเปิดสไลด์ขึ้นมาแล้วคำถามแรกที่ผุดขึ้นคือ "ข้อมูลนี้ถูกเข้ารหัสด้วยคุณสมบัติอะไร" แทนที่จะเป็น "กราฟนี้สวยไหม" แปลว่าเล่มนี้ทำงานของมันเสร็จแล้ว
สิ่งที่คุณได้กลับไปไม่ใช่ความจำ มันคือลำดับการถาม — ช่องทางไหน · ตัวหารคืออะไร · คนอ่านต้องทำงานอะไรแทนคุณบ้าง · อะไรที่ภาพส่งไปไม่ถึง แล้วมันไปอยู่ที่ไหนแทน
และเมื่อไหร่ที่คุณเจอสถานการณ์ที่เล่มนี้ไม่ได้พูดถึง คุณจะไม่ต้องรอให้ใครออกกฎข้อใหม่มาให้ เพราะกลไกที่อยู่ใต้กฎทุกข้อ คุณมีมันแล้ว
กฎบอกคุณว่าต้องทำอะไร กลไกบอกคุณว่าทำไม และมีแค่อย่างหลังที่ตามคุณไปได้ในวันที่โจทย์เปลี่ยน
อ่านต่อ: ราคา vs มูลค่า#
สิบบทแรกเป็นเรื่องของชั้นล่าง — ตัวเลขหนึ่งชุดเดินทางเข้าสายตาคนอ่านยังไง และหายไปตรงไหนบ้าง · บทที่ 11 ถึง 13 ขึ้นมาอีกชั้น คือเมื่อของหลายชิ้นอยู่บนหน้าเดียวกัน และเมื่อประโยคเดียวต้องแบกสิ่งที่ภาพแบกไม่ไหว · สองบทสุดท้ายเอาทั้งหมดมาประกอบลงบนสไลด์จริง แล้วบีบให้เหลือเช็กลิสต์ที่พกติดตัวไปใช้ต่อได้
ถ้าอยากลองใช้สิ่งที่เพิ่งอ่านกับข้อมูลจริงที่มีเดิมพัน งบการเงินคือสนามที่ดีที่สุด เพราะมันเต็มไปด้วยตัวเลขที่ต่างกันไม่กี่เปอร์เซ็นต์แต่เปลี่ยนคำตอบทั้งหมด และเต็มไปด้วยกราฟที่แกนถูกซูมจนคุณต้องระวังตัวตลอดเวลา — ซึ่งตอนนี้คุณวัดออกมาเป็นตัวเลขได้แล้วว่ามันซูมไปกี่เท่า
คู่มือหุ้น — ราคา vs มูลค่า — อ่านงบการเงินเป็นด้วยตัวเอง และแยกออกว่าตัวเลขตรงหน้าคือราคาที่ตลาดตกลงกัน หรือมูลค่าที่กิจการสร้างจริง
ถ้าเนื้อหานี้มีประโยชน์ —เลี้ยงกาแฟสักแก้ว