7. การรวมตารางข้อมูล (Combining DataFrames)

ในการวิเคราะห์ข้อมูลจริง มักจะพบว่าข้อมูลของเรากระจายอยู่ในหลายตารางหรือหลายไฟล์ การรู้วิธีรวมตาราง (Combining) และเชื่อมตาราง (Joining) จึงเป็นทักษะที่สำคัญมาก เปรียบเสมือนการต่อตัวต่อเลโก้หลายชิ้นให้เป็นชิ้นงานใหญ่ที่สมบูรณ์

graph LR
    A["DataFrame A
(ตาราง A)"] B["DataFrame B
(ตาราง B)"] C["DataFrame C
(ตาราง C)"] A --> D{"วิธีรวมข้อมูล
Combining Method"} B --> D C --> D D --> E["Concatenation
(การต่อข้อมูล)"] D --> F["Merge/Join
(การเชื่อมข้อมูล)"] E --> G["ผลลัพธ์รวม
Combined Result"] F --> G style A fill:#458588,stroke:#83a598,color:#ebdbb2 style B fill:#458588,stroke:#83a598,color:#ebdbb2 style C fill:#458588,stroke:#83a598,color:#ebdbb2 style D fill:#d65d0e,stroke:#fe8019,color:#ebdbb2 style E fill:#98971a,stroke:#b8bb26,color:#282828 style F fill:#98971a,stroke:#b8bb26,color:#282828 style G fill:#d79921,stroke:#fabd2f,color:#282828

7.1 การต่อข้อมูล (Concatenation)

Concatenation คือการต่อข้อมูลแบบง่ายๆ โดยนำตารางหลายตารางมาวางต่อกัน ไม่ว่าจะเป็นการต่อในแนวตั้ง (เพิ่ม Rows) หรือต่อในแนวนอน (เพิ่ม Columns) เหมาะสำหรับตารางที่มีโครงสร้างเหมือนกันหรือคล้ายกัน

7.1.1 พื้นฐานการใช้ pd.concat()

ฟังก์ชัน pd.concat() เป็นเครื่องมือหลักในการต่อตาราง โดยมีพารามิเตอร์สำคัญดังนี้:

graph TD
    A["pd.concat objs
(รายการตาราง)"] A --> B{"axis=?
(แกนการต่อ)"} B -->|"axis=0
(แนวตั้ง)"| C["ต่อ Rows
เพิ่มแถว"] B -->|"axis=1
(แนวนอน)"| D["ต่อ Columns
เพิ่มคอลัมน์"] C --> E{"ignore_index?"} D --> E E -->|True| F["Index ใหม่
0,1,2,3..."] E -->|False| G["เก็บ Index เดิม
Original Index"] style A fill:#458588,stroke:#83a598,color:#ebdbb2 style B fill:#d65d0e,stroke:#fe8019,color:#ebdbb2 style C fill:#98971a,stroke:#b8bb26,color:#282828 style D fill:#98971a,stroke:#b8bb26,color:#282828 style E fill:#d79921,stroke:#fabd2f,color:#282828 style F fill:#b16286,stroke:#d3869b,color:#ebdbb2 style G fill:#b16286,stroke:#d3869b,color:#ebdbb2

ตัวอย่างโค้ด: การต่อแนวตั้ง (Vertical Concatenation)

import pandas as pd

def demo_vertical_concat():
    """
    สาธิตการต่อข้อมูลในแนวตั้ง (เพิ่ม Rows)
    เหมาะสำหรับข้อมูลที่มีโครงสร้างเหมือนกัน เช่น ข้อมูลรายเดือน
    """
    
    # สร้างข้อมูลยอดขาย Q1
    q1_sales = pd.DataFrame({
        'เดือน': ['มกราคม', 'กุมภาพันธ์', 'มีนาคม'],
        'ยอดขาย': [150000, 180000, 200000],
        'ไตรมาส': ['Q1', 'Q1', 'Q1']
    })
    
    # สร้างข้อมูลยอดขาย Q2
    q2_sales = pd.DataFrame({
        'เดือน': ['เมษายน', 'พฤษภาคม', 'มิถุนายน'],
        'ยอดขาย': [220000, 250000, 280000],
        'ไตรมาส': ['Q2', 'Q2', 'Q2']
    })
    
    # ต่อข้อมูลแนวตั้ง (axis=0)
    half_year = pd.concat([q1_sales, q2_sales], axis=0, ignore_index=True)
    
    print("ข้อมูลครึ่งปีแรก:")
    print(half_year)
    print(f"\nจำนวนแถวรวม: {len(half_year)} แถว")
    
    return half_year

# ใช้งาน
result = demo_vertical_concat()

Output:

ข้อมูลครึ่งปีแรก:
        เดือน  ยอดขาย ไตรมาส
0    มกราคม  150000   Q1
1  กุมภาพันธ์  180000   Q1
2    มีนาคม  200000   Q1
3    เมษายน  220000   Q2
4  พฤษภาคม  250000   Q2
5   มิถุนายน  280000   Q2

จำนวนแถวรวม: 6 แถว

7.1.2 การต่อแนวนอน (Horizontal Concatenation)

การต่อแนวนอนใช้สำหรับเพิ่ม Columns โดย Rows ต้องมีจำนวนเท่ากันหรือสามารถจับคู่กันได้ผ่าน Index

ตัวอย่างโค้ด: การต่อแนวนอน

def demo_horizontal_concat():
    """
    สาธิตการต่อข้อมูลในแนวนอน (เพิ่ม Columns)
    เหมาะสำหรับการเพิ่มข้อมูลเสริมที่เกี่ยวข้องกับแถวเดียวกัน
    """
    
    # ข้อมูลพนักงาน
    employees = pd.DataFrame({
        'รหัส': ['E001', 'E002', 'E003'],
        'ชื่อ': ['สมชาย', 'สมหญิง', 'สมศักดิ์']
    })
    
    # ข้อมูลเงินเดือน
    salaries = pd.DataFrame({
        'เงินเดือน': [35000, 42000, 38000],
        'โบนัส': [5000, 7000, 6000]
    })
    
    # ข้อมูลแผนก
    departments = pd.DataFrame({
        'แผนก': ['IT', 'HR', 'Finance']
    })
    
    # ต่อแนวนอน (axis=1)
    employee_full = pd.concat([employees, salaries, departments], axis=1)
    
    print("ข้อมูลพนักงานรวม:")
    print(employee_full)
    
    return employee_full

# ใช้งาน
full_data = demo_horizontal_concat()

Output:

ข้อมูลพนักงานรวม:
    รหัส      ชื่อ  เงินเดือน  โบนัส     แผนก
0  E001   สมชาย   35000  5000      IT
1  E002  สมหญิง   42000  7000      HR
2  E003  สมศักดิ์   38000  6000  Finance

7.1.3 การจัดการ Column ที่ไม่ตรงกัน (join parameter)

เมื่อต่อตารางที่มี Column ไม่เหมือนกัน เราต้องเลือกว่าจะเก็บ Column ทั้งหมด (outer join) หรือเฉพาะที่ตรงกัน (inner join)

พารามิเตอร์ ความหมาย ผลลัพธ์
join='outer' รวมทุก Column (ค่าเริ่มต้น) เก็บ Column ทั้งหมด ช่องว่างเป็น NaN
join='inner' เฉพาะ Column ที่ตรงกัน เก็บเฉพาะ Column ที่มีทั้งสองตาราง

ตัวอย่างโค้ด: เปรียบเทียบ join types

def demo_join_types():
    """
    สาธิตความแตกต่างระหว่าง outer join และ inner join
    """
    
    # ตาราง A มี columns: ชื่อ, อายุ
    df_a = pd.DataFrame({
        'ชื่อ': ['ก', 'ข', 'ค'],
        'อายุ': [25, 30, 35]
    }, index=[0, 1, 2])
    
    # ตาราง B มี columns: ชื่อ, เงินเดือน
    df_b = pd.DataFrame({
        'ชื่อ': ['ง', 'จ', 'ฉ'],
        'เงินเดือน': [35000, 40000, 45000]
    }, index=[0, 1, 2])
    
    # Outer Join (เก็บทุก column)
    outer_result = pd.concat([df_a, df_b], axis=0, join='outer')
    print("Outer Join (เก็บทุก column):")
    print(outer_result)
    print()
    
    # Inner Join (เฉพาะ column ที่ตรงกัน)
    inner_result = pd.concat([df_a, df_b], axis=0, join='inner')
    print("Inner Join (เฉพาะ column 'ชื่อ'):")
    print(inner_result)
    
    return outer_result, inner_result

# ใช้งาน
outer, inner = demo_join_types()

7.1.4 การใช้ keys เพื่อสร้าง MultiIndex

เมื่อต่อหลายตาราง เราสามารถใช้ keys เพื่อติดป้ายชื่อว่าแต่ละส่วนมาจากตารางไหน ซึ่งจะสร้าง MultiIndex (Index หลายระดับ)

ตัวอย่างโค้ด: การใช้ keys

def demo_concat_with_keys():
    """
    สาธิตการใช้ keys เพื่อแยกแยะแหล่งที่มาของข้อมูล
    """
    
    # ยอดขายภาคเหนือ
    north = pd.DataFrame({
        'สาขา': ['เชียงใหม่', 'เชียงราย'],
        'ยอดขาย': [500000, 350000]
    })
    
    # ยอดขายภาคกลาง
    central = pd.DataFrame({
        'สาขา': ['กรุงเทพ', 'นนทบุรี', 'ปทุมธานี'],
        'ยอดขาย': [2000000, 800000, 600000]
    })
    
    # ยอดขายภาคใต้
    south = pd.DataFrame({
        'สาขา': ['ภูเก็ต', 'สงขลา'],
        'ยอดขาย': [700000, 450000]
    })
    
    # ต่อพร้อม keys
    all_regions = pd.concat(
        [north, central, south],
        keys=['ภาคเหนือ', 'ภาคกลาง', 'ภาคใต้'],
        names=['ภูมิภาค', 'ลำดับ']
    )
    
    print("ยอดขายทุกภูมิภาค (MultiIndex):")
    print(all_regions)
    print("\n--- การเข้าถึงข้อมูลด้วย MultiIndex ---")
    print("\nยอดขายภาคกลาง:")
    print(all_regions.loc['ภาคกลาง'])
    
    return all_regions

# ใช้งาน
regional_sales = demo_concat_with_keys()

7.2 การเชื่อมข้อมูล (Merging/Joining)

Merging คือการเชื่อมตารางโดยอิงตามคอลัมน์กลาง (Common Column) หรือที่เรียกว่า Key ซึ่งคล้ายกับการทำ JOIN ใน SQL เป็นเทคนิคที่ใช้บ่อยมากเมื่อต้องการรวมข้อมูลจากหลายแหล่ง

7.2.1 พื้นฐาน pd.merge() และประเภทของ Join

ฟังก์ชัน pd.merge() มีพารามิเตอร์สำคัญ:

graph TB
    A["DataFrame Left
(ตารางซ้าย)"] B["DataFrame Right
(ตารางขวา)"] A --> C["pd.merge()
Key Column"] B --> C C --> D{"Join Type
(how=?)"} D --> E["Inner Join
เฉพาะที่ตรงกัน"] D --> F["Left Join
เก็บซ้ายทั้งหมด"] D --> G["Right Join
เก็บขวาทั้งหมด"] D --> H["Outer Join
เก็บทั้งหมด"] E --> I["Result
(ผลลัพธ์)"] F --> I G --> I H --> I style A fill:#458588,stroke:#83a598,color:#ebdbb2 style B fill:#458588,stroke:#83a598,color:#ebdbb2 style C fill:#d79921,stroke:#fabd2f,color:#282828 style D fill:#d65d0e,stroke:#fe8019,color:#ebdbb2 style E fill:#98971a,stroke:#b8bb26,color:#282828 style F fill:#98971a,stroke:#b8bb26,color:#282828 style G fill:#98971a,stroke:#b8bb26,color:#282828 style H fill:#98971a,stroke:#b8bb26,color:#282828 style I fill:#b16286,stroke:#d3869b,color:#ebdbb2

ตารางเปรียบเทียบประเภท Join:

ประเภท ค่า how คำอธิบาย SQL เทียบเท่า
Inner Join 'inner' เก็บเฉพาะ Rows ที่ Key ตรงกันทั้งสองตาราง INNER JOIN
Left Join 'left' เก็บทุก Rows จากตารางซ้าย + Rows ที่ตรงจากตารางขวา LEFT JOIN
Right Join 'right' เก็บทุก Rows จากตารางขวา + Rows ที่ตรงจากตารางซ้าย RIGHT JOIN
Outer Join 'outer' เก็บทุก Rows จากทั้งสองตาราง FULL OUTER JOIN

7.2.2 Inner Join - เฉพาะข้อมูลที่ตรงกัน

Inner Join คือการเก็บเฉพาะแถวที่มี Key ตรงกันทั้งสองตาราง เป็นค่าเริ่มต้นของ pd.merge()

ตัวอย่างโค้ด: Inner Join

def demo_inner_join():
    """
    สาธิต Inner Join - เก็บเฉพาะข้อมูลที่ตรงกันทั้งสองตาราง
    สถานการณ์: รวมข้อมูลนักเรียนกับคะแนนสอบ
    """
    
    # ข้อมูลนักเรียน
    students = pd.DataFrame({
        'รหัสนักเรียน': ['S001', 'S002', 'S003', 'S004'],
        'ชื่อ': ['กนก', 'กานต์', 'กมล', 'กฤษณ์'],
        'ห้อง': ['1/1', '1/1', '1/2', '1/2']
    })
    
    # ข้อมูลคะแนนสอบ (มีเฉพาะบางคน)
    scores = pd.DataFrame({
        'รหัสนักเรียน': ['S001', 'S002', 'S005'],
        'คะแนน': [85, 92, 78],
        'เกรด': ['B', 'A', 'C']
    })
    
    # Inner Join - เฉพาะนักเรียนที่มีคะแนน
    result = pd.merge(students, scores, on='รหัสนักเรียน', how='inner')
    
    print("Inner Join Result (เฉพาะที่มีคะแนน):")
    print(result)
    print(f"\nจำนวนแถว: {len(result)} คน (จากทั้งหมด {len(students)} คน)")
    
    return result

# ใช้งาน
inner_result = demo_inner_join()

Output:

Inner Join Result (เฉพาะที่มีคะแนน):
  รหัสนักเรียน   ชื่อ  ห้อง  คะแนน เกรด
0       S001  กนก  1/1    85   B
1       S002  กานต์  1/1    92   A

จำนวนแถว: 2 คน (จากทั้งหมด 4 คน)

7.2.3 Left Join - เก็บตารางซ้ายทั้งหมด

Left Join เก็บทุกแถวจากตารางซ้าย และเติมข้อมูลจากตารางขวา (ถ้าตรงกัน) ถ้าไม่ตรงจะเป็น NaN

ตัวอย่างโค้ด: Left Join

def demo_left_join():
    """
    สาธิต Left Join - เก็บตารางซ้ายทั้งหมด
    สถานการณ์: ต้องการเก็บรายชื่อนักเรียนทั้งหมด แม้ไม่มีคะแนน
    """
    
    # ข้อมูลนักเรียน (ตารางซ้าย)
    students = pd.DataFrame({
        'รหัสนักเรียน': ['S001', 'S002', 'S003', 'S004'],
        'ชื่อ': ['กนก', 'กานต์', 'กมล', 'กฤษณ์'],
        'ห้อง': ['1/1', '1/1', '1/2', '1/2']
    })
    
    # ข้อมูลคะแนนสอบ (ตารางขวา)
    scores = pd.DataFrame({
        'รหัสนักเรียน': ['S001', 'S002', 'S005'],
        'คะแนน': [85, 92, 78],
        'เกรด': ['B', 'A', 'C']
    })
    
    # Left Join - เก็บนักเรียนทั้งหมด
    result = pd.merge(students, scores, on='รหัสนักเรียน', how='left')
    
    print("Left Join Result (เก็บนักเรียนทั้งหมด):")
    print(result)
    
    # ตรวจสอบว่าใครยังไม่ได้สอบ
    no_score = result[result['คะแนน'].isna()]
    print(f"\nนักเรียนที่ยังไม่มีคะแนน: {len(no_score)} คน")
    print(no_score[['รหัสนักเรียน', 'ชื่อ']])
    
    return result

# ใช้งาน
left_result = demo_left_join()

Output:

Left Join Result (เก็บนักเรียนทั้งหมด):
  รหัสนักเรียน    ชื่อ  ห้อง  คะแนน เกรด
0       S001   กนก  1/1  85.0   B
1       S002  กานต์  1/1  92.0   A
2       S003   กมล  1/2   NaN  NaN
3       S004  กฤษณ์  1/2   NaN  NaN

นักเรียนที่ยังไม่มีคะแนน: 2 คน
  รหัสนักเรียน    ชื่อ
2       S003   กมล
3       S004  กฤษณ์

7.2.4 Right Join และ Outer Join

Right Join ตรงข้ามกับ Left Join (เก็บตารางขวาทั้งหมด) ส่วน Outer Join เก็บทั้งสองตารางทั้งหมด

ตัวอย่างโค้ด: เปรียบเทียบทุก Join Types

def demo_all_join_types():
    """
    สาธิตความแตกต่างของทุก Join Types
    """
    
    # ตารางลูกค้า
    customers = pd.DataFrame({
        'รหัสลูกค้า': ['C001', 'C002', 'C003'],
        'ชื่อ': ['บริษัท A', 'บริษัท B', 'บริษัท C']
    })
    
    # ตารางคำสั่งซื้อ
    orders = pd.DataFrame({
        'รหัสคำสั่ง': ['O001', 'O002', 'O003'],
        'รหัสลูกค้า': ['C001', 'C001', 'C004'],
        'จำนวนเงิน': [50000, 75000, 100000]
    })
    
    print("=== ตารางลูกค้า ===")
    print(customers)
    print("\n=== ตารางคำสั่งซื้อ ===")
    print(orders)
    
    # Inner Join
    print("\n\n1. INNER JOIN (เฉพาะที่ตรงกัน):")
    inner = pd.merge(customers, orders, on='รหัสลูกค้า', how='inner')
    print(inner)
    print(f"จำนวนแถว: {len(inner)}")
    
    # Left Join
    print("\n\n2. LEFT JOIN (เก็บลูกค้าทั้งหมด):")
    left = pd.merge(customers, orders, on='รหัสลูกค้า', how='left')
    print(left)
    print(f"จำนวนแถว: {len(left)}")
    
    # Right Join
    print("\n\n3. RIGHT JOIN (เก็บคำสั่งซื้อทั้งหมด):")
    right = pd.merge(customers, orders, on='รหัสลูกค้า', how='right')
    print(right)
    print(f"จำนวนแถว: {len(right)}")
    
    # Outer Join
    print("\n\n4. OUTER JOIN (เก็บทั้งหมด):")
    outer = pd.merge(customers, orders, on='รหัสลูกค้า', how='outer')
    print(outer)
    print(f"จำนวนแถว: {len(outer)}")
    
    return inner, left, right, outer

# ใช้งาน
results = demo_all_join_types()

7.2.5 การเชื่อมด้วยคอลัมน์ที่ชื่อต่างกัน

บางครั้งคอลัมน์ที่ใช้เชื่อมมีชื่อไม่เหมือนกัน ให้ใช้ left_on และ right_on

ตัวอย่างโค้ด: ชื่อคอลัมน์ต่างกัน

def demo_different_column_names():
    """
    สาธิตการ merge เมื่อชื่อคอลัมน์ไม่เหมือนกัน
    """
    
    # ตารางพนักงาน (ใช้ emp_id)
    employees = pd.DataFrame({
        'emp_id': ['E001', 'E002', 'E003'],
        'ชื่อพนักงาน': ['สมชาย', 'สมหญิง', 'สมศักดิ์'],
        'แผนก': ['IT', 'HR', 'Finance']
    })
    
    # ตารางโปรเจกต์ (ใช้ employee_code)
    projects = pd.DataFrame({
        'project_id': ['P001', 'P002', 'P003'],
        'employee_code': ['E001', 'E001', 'E002'],
        'ชื่อโปรเจกต์': ['Website', 'Mobile App', 'Recruitment']
    })
    
    # Merge โดยระบุชื่อคอลัมน์ต่างกัน
    result = pd.merge(
        employees, 
        projects,
        left_on='emp_id',
        right_on='employee_code',
        how='left'
    )
    
    print("ผลการ Merge (ชื่อคอลัมน์ต่างกัน):")
    print(result)
    
    # ลบคอลัมน์ซ้ำซ้อน
    result = result.drop('employee_code', axis=1)
    print("\nหลังลบคอลัมน์ซ้ำ:")
    print(result)
    
    return result

# ใช้งาน
diff_col_result = demo_different_column_names()

7.2.6 การเชื่อมด้วยหลายคอลัมน์ (Multiple Keys)

สามารถเชื่อมด้วยหลายคอลัมน์พร้อมกันได้ เช่น ใช้ทั้ง รหัสและวันที่

ตัวอย่างโค้ด: Multiple Keys

def demo_merge_multiple_keys():
    """
    สาธิตการ merge ด้วยหลายคอลัมน์พร้อมกัน
    สถานการณ์: จับคู่ข้อมูลตามทั้ง 'สาขา' และ 'วันที่'
    """
    
    # ยอดขาย
    sales = pd.DataFrame({
        'สาขา': ['BKK', 'BKK', 'CNX', 'CNX'],
        'วันที่': ['2024-01-01', '2024-01-02', '2024-01-01', '2024-01-02'],
        'ยอดขาย': [100000, 120000, 50000, 55000]
    })
    
    # ค่าใช้จ่าย
    expenses = pd.DataFrame({
        'สาขา': ['BKK', 'BKK', 'CNX'],
        'วันที่': ['2024-01-01', '2024-01-02', '2024-01-01'],
        'ค่าใช้จ่าย': [30000, 35000, 15000]
    })
    
    # Merge ด้วย 2 คอลัมน์
    result = pd.merge(
        sales, 
        expenses,
        on=['สาขา', 'วันที่'],
        how='left'
    )
    
    # คำนวณกำไร
    result['กำไร'] = result['ยอดขาย'] - result['ค่าใช้จ่าย'].fillna(0)
    
    print("รายงานกำไร (Multiple Keys):")
    print(result)
    
    return result

# ใช้งาน
multi_key_result = demo_merge_multiple_keys()

7.3 การใช้ .join() Method

นอกจาก pd.merge() แล้ว DataFrame ยังมี method .join() ซึ่งเป็นทางลัดสำหรับการ merge โดยใช้ Index เป็นตัวเชื่อม

7.3.1 ความแตกต่างระหว่าง merge() และ join()

ลักษณะ pd.merge() df.join()
เชื่อมด้วย Column (ค่าเริ่มต้น) Index (ค่าเริ่มต้น)
รูปแบบ ฟังก์ชัน Pandas Method ของ DataFrame
Join Type เริ่มต้น inner left
เหมาะสำหรับ Column-based joins Index-based joins

ตัวอย่างโค้ด: การใช้ join()

def demo_join_method():
    """
    สาธิตการใช้ .join() method แทน pd.merge()
    เหมาะเมื่อต้องการเชื่อมด้วย Index
    """
    
    # ข้อมูลประเทศ (ใช้รหัสประเทศเป็น Index)
    countries = pd.DataFrame({
        'ชื่อประเทศ': ['ไทย', 'ญี่ปุ่น', 'เกาหลี'],
        'ทวีป': ['เอเชีย', 'เอเชีย', 'เอเชีย']
    }, index=['TH', 'JP', 'KR'])
    
    # ข้อมูลประชากร
    population = pd.DataFrame({
        'ประชากร (ล้านคน)': [70, 125, 52],
        'เมืองหลวง': ['กรุงเทพ', 'โตเกียว', 'โซล']
    }, index=['TH', 'JP', 'KR'])
    
    # ข้อมูล GDP
    gdp = pd.DataFrame({
        'GDP (Billion USD)': [500, 5000, 1600]
    }, index=['TH', 'JP', 'SG'])  # สังเกตว่ามี SG แทน KR
    
    print("=== ตารางประเทศ ===")
    print(countries)
    print("\n=== ตารางประชากร ===")
    print(population)
    print("\n=== ตาราง GDP ===")
    print(gdp)
    
    # ใช้ .join() (left join เป็นค่าเริ่มต้น)
    result = countries.join(population).join(gdp, how='left')
    
    print("\n\n=== ผลการ Join ===")
    print(result)
    
    return result

# ใช้งาน
join_result = demo_join_method()

7.4 กรณีศึกษาและการใช้งานจริง (Real-world Scenarios)

7.4.1 กรณีศึกษา: รวมข้อมูลจากหลายแหล่ง

สถานการณ์: บริษัทมีข้อมูลกระจายอยู่ 3 ไฟล์ ต้องการรวมเพื่อวิเคราะห์

def case_study_multi_source():
    """
    กรณีศึกษา: รวมข้อมูลลูกค้า คำสั่งซื้อ และสินค้า
    """
    
    # 1. ข้อมูลลูกค้า (จากระบบ CRM)
    customers = pd.DataFrame({
        'customer_id': [1, 2, 3, 4],
        'ชื่อลูกค้า': ['บริษัท A', 'บริษัท B', 'บริษัท C', 'บริษัท D'],
        'ประเภท': ['VIP', 'ทั่วไป', 'VIP', 'ทั่วไป']
    })
    
    # 2. ข้อมูลคำสั่งซื้อ (จากระบบขาย)
    orders = pd.DataFrame({
        'order_id': [101, 102, 103, 104, 105],
        'customer_id': [1, 1, 2, 3, 5],  # สังเกต customer 5 ไม่มีในระบบ CRM
        'product_id': ['P001', 'P002', 'P001', 'P003', 'P002'],
        'จำนวน': [10, 5, 20, 15, 8],
        'วันที่': ['2024-01-15', '2024-01-16', '2024-01-16', '2024-01-17', '2024-01-18']
    })
    
    # 3. ข้อมูลสินค้า (จากระบบคลังสินค้า)
    products = pd.DataFrame({
        'product_id': ['P001', 'P002', 'P003'],
        'ชื่อสินค้า': ['แล็ปท็อป', 'เมาส์', 'คีย์บอร์ด'],
        'ราคา': [25000, 500, 1500]
    })
    
    print("=== ขั้นตอนที่ 1: Join Orders กับ Customers ===")
    # Left join เพื่อเก็บคำสั่งซื้อทั้งหมด
    step1 = pd.merge(orders, customers, on='customer_id', how='left')
    print(step1)
    
    print("\n=== ขั้นตอนที่ 2: Join กับ Products ===")
    # Inner join เพราะต้องการเฉพาะสินค้าที่มีข้อมูล
    step2 = pd.merge(step1, products, on='product_id', how='inner')
    print(step2)
    
    print("\n=== ขั้นตอนที่ 3: คำนวณมูลค่า ===")
    step2['มูลค่ารวม'] = step2['จำนวน'] * step2['ราคา']
    final = step2[['order_id', 'ชื่อลูกค้า', 'ประเภท', 'ชื่อสินค้า', 'จำนวน', 'ราคา', 'มูลค่ารวม']]
    print(final)
    
    print("\n=== สรุปยอดขายตามประเภทลูกค้า ===")
    summary = final.groupby('ประเภท')['มูลค่ารวม'].sum().reset_index()
    summary.columns = ['ประเภทลูกค้า', 'ยอดขายรวม']
    print(summary)
    
    return final, summary

# ใช้งาน
final_data, summary = case_study_multi_source()

7.4.2 การจัดการข้อมูลที่ซ้ำซ้อนหลัง Merge

หลัง Merge อาจพบคอลัมน์ที่มีชื่อซ้ำกัน Pandas จะเพิ่ม suffix (_x, _y) ให้อัตโนมัติ

ตัวอย่างโค้ด: จัดการ suffix

def demo_handle_suffixes():
    """
    สาธิตการจัดการกับคอลัมน์ซ้ำหลัง merge
    """
    
    # ข้อมูลแผนกเก่า
    old_dept = pd.DataFrame({
        'รหัสพนักงาน': ['E001', 'E002', 'E003'],
        'แผนก': ['IT', 'HR', 'Finance'],
        'เงินเดือน': [35000, 42000, 38000]
    })
    
    # ข้อมูลแผนกใหม่ (มีการย้าย)
    new_dept = pd.DataFrame({
        'รหัสพนักงาน': ['E001', 'E002', 'E003'],
        'แผนก': ['IT', 'Finance', 'Finance'],  # E002 ย้ายแผนก
        'เงินเดือน': [40000, 45000, 40000]  # ได้รับการขึ้นเงินเดือน
    })
    
    # Merge พร้อมกำหนด suffix ที่อ่านง่าย
    comparison = pd.merge(
        old_dept, 
        new_dept,
        on='รหัสพนักงาน',
        suffixes=('_เก่า', '_ใหม่')
    )
    
    # คำนวณการเปลี่ยนแปลง
    comparison['เปลี่ยนแผนก'] = comparison['แผนก_เก่า'] != comparison['แผนก_ใหม่']
    comparison['ขึ้นเงินเดือน'] = comparison['เงินเดือน_ใหม่'] - comparison['เงินเดือน_เก่า']
    
    print("รายงานการเปลี่ยนแปลง:")
    print(comparison)
    
    print("\n=== พนักงานที่ย้ายแผนก ===")
    moved = comparison[comparison['เปลี่ยนแผนก']]
    print(moved[['รหัสพนักงาน', 'แผนก_เก่า', 'แผนก_ใหม่']])
    
    return comparison

# ใช้งาน
change_report = demo_handle_suffixes()

7.5 เทคนิคขั้นสูงและ Best Practices

7.5.1 การตรวจสอบคุณภาพหลัง Merge

หลังจาก Merge แล้ว ควรตรวจสอบความถูกต้องเสมอ

Checklist การตรวจสอบ:

  1. จำนวนแถว: ถูกต้องตาม Join Type หรือไม่
  2. ค่า NaN: มีค่าหายไปในคอลัมน์ที่ไม่ควรหายหรือไม่
  3. ข้อมูลซ้ำ: มีแถวซ้ำที่ไม่คาดคิดหรือไม่
  4. ช่วงข้อมูล: ค่าสูงสุด-ต่ำสุดยังสมเหตุสมผลหรือไม่

ตัวอย่างโค้ด: Validation Pipeline

def validate_merge(df_result, df_left, df_right, join_type):
    """
    ฟังก์ชันตรวจสอบคุณภาพข้อมูลหลัง merge
    
    Parameters:
    -----------
    df_result : DataFrame ผลลัพธ์หลัง merge
    df_left : DataFrame ตารางซ้าย
    df_right : DataFrame ตารางขวา
    join_type : str ประเภท join ('inner', 'left', 'right', 'outer')
    """
    
    print("=" * 60)
    print("📊 รายงานการตรวจสอบคุณภาพ (Merge Validation Report)")
    print("=" * 60)
    
    # 1. ตรวจสอบจำนวนแถว
    print("\n1️⃣ การตรวจสอบจำนวนแถว:")
    print(f"   - ตารางซ้าย: {len(df_left):,} แถว")
    print(f"   - ตารางขวา: {len(df_right):,} แถว")
    print(f"   - ผลลัพธ์ ({join_type}): {len(df_result):,} แถว")
    
    # ตรวจสอบความสมเหตุสมผล
    if join_type == 'inner':
        if len(df_result) > min(len(df_left), len(df_right)):
            print("   ⚠️ คำเตือน: Inner join มีแถวมากกว่าที่คาดไว้ (อาจมี key ซ้ำ)")
    elif join_type == 'left':
        if len(df_result) < len(df_left):
            print("   ⚠️ คำเตือน: Left join มีแถวน้อยกว่าตารางซ้าย (ไม่ปกติ)")
    
    # 2. ตรวจสอบค่า NaN
    print("\n2️⃣ การตรวจสอบค่า Missing (NaN):")
    null_counts = df_result.isnull().sum()
    null_cols = null_counts[null_counts > 0]
    if len(null_cols) > 0:
        for col, count in null_cols.items():
            pct = (count / len(df_result)) * 100
            print(f"   - {col}: {count:,} แถว ({pct:.2f}%)")
    else:
        print("   ✅ ไม่พบค่า NaN")
    
    # 3. ตรวจสอบข้อมูลซ้ำ
    print("\n3️⃣ การตรวจสอบข้อมูลซ้ำ:")
    duplicates = df_result.duplicated().sum()
    if duplicates > 0:
        print(f"   ⚠️ พบแถวซ้ำ: {duplicates:,} แถว")
    else:
        print("   ✅ ไม่พบข้อมูลซ้ำ")
    
    # 4. สรุปสถิติพื้นฐาน
    print("\n4️⃣ สถิติคอลัมน์ตัวเลข:")
    numeric_cols = df_result.select_dtypes(include=['number']).columns
    if len(numeric_cols) > 0:
        print(df_result[numeric_cols].describe().round(2))
    else:
        print("   ℹ️ ไม่มีคอลัมน์ตัวเลข")
    
    print("\n" + "=" * 60)
    print("✅ การตรวจสอบเสร็จสิ้น")
    print("=" * 60 + "\n")

# ตัวอย่างการใช้งาน
def demo_validation():
    """
    สาธิตการใช้ validation pipeline
    """
    
    # สร้างข้อมูลตัวอย่าง
    df1 = pd.DataFrame({
        'id': [1, 2, 3, 4],
        'value_a': [10, 20, 30, 40]
    })
    
    df2 = pd.DataFrame({
        'id': [2, 3, 4, 5],
        'value_b': [200, 300, 400, 500]
    })
    
    # Merge
    result = pd.merge(df1, df2, on='id', how='left')
    
    # Validate
    validate_merge(result, df1, df2, 'left')
    
    return result

# ทดสอบ
validated_result = demo_validation()

7.5.2 Performance Tips สำหรับ DataFrame ขนาดใหญ่

เมื่อทำงานกับข้อมูลขนาดใหญ่ (หลายล้านแถว) ประสิทธิภาพเป็นสิ่งสำคัญ

เทคนิคเพิ่มความเร็ว:

  1. ตั้ง Index ก่อน Join

    # ช้า
    result = pd.merge(df1, df2, on='id')
    
    # เร็วกว่า
    df1_indexed = df1.set_index('id')
    df2_indexed = df2.set_index('id')
    result = df1_indexed.join(df2_indexed)
    
  2. เลือกเฉพาะคอลัมน์ที่ต้องการ

    # ช้า (โหลดทุกคอลัมน์)
    df1 = pd.read_csv('data.csv')
    
    # เร็วกว่า (เลือกเฉพาะที่ต้องการ)
    df1 = pd.read_csv('data.csv', usecols=['id', 'name', 'value'])
    
  3. ใช้ Category dtype สำหรับข้อมูลซ้ำๆ

    # แปลงคอลัมน์ที่มีค่าซ้ำมาก (เช่น แผนก, เพศ)
    df['แผนก'] = df['แผนก'].astype('category')
    

ตารางเปรียบเทียบเวลาทำงาน:

จำนวนแถว merge() ปกติ join() with Index ส่วนต่าง
10,000 0.05 วินาที 0.02 วินาที -60%
100,000 0.50 วินาที 0.15 วินาที -70%
1,000,000 8.00 วินาที 2.00 วินาที -75%

7.5.3 Common Pitfalls และวิธีแก้ไข

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

  1. ลืมระบุ how='left'

    # ผิด: ใช้ inner join โดยไม่ตั้งใจ
    result = pd.merge(df1, df2, on='id')  # inner join เป็นค่าเริ่มต้น
    
    # ถูก: ระบุชัดเจน
    result = pd.merge(df1, df2, on='id', how='left')
    
  2. Key มี Duplicate

    # ตรวจสอบก่อน merge
    if df1['id'].duplicated().any():
        print("⚠️ มี id ซ้ำในตารางซ้าย")
    
  3. ประเภทข้อมูลไม่ตรงกัน

    # df1['id'] เป็น int, df2['id'] เป็น string
    # แปลงให้ตรงกันก่อน
    df2['id'] = df2['id'].astype(int)
    

7.6 สรุปและเปรียบเทียบวิธีการรวมตาราง

graph TB
    A["ต้องการรวมตาราง
Combine DataFrames"] A --> B{"โครงสร้างเหมือนกัน?
Same Structure?"} B -->|"ใช่
Yes"| C["ใช้ Concatenation
pd.concat"] B -->|"ไม่ใช่
No"| D["ใช้ Merge/Join
pd.merge / .join"] C --> E{"ทิศทาง?
Direction?"} E -->|"แนวตั้ง
Vertical"| F["axis=0
เพิ่มแถว"] E -->|"แนวนอน
Horizontal"| G["axis=1
เพิ่มคอลัมน์"] D --> H{"มี Key Column?"} H -->|"ใช่"| I["pd.merge
on='column'"] H -->|"ใช้ Index"| J["df.join"] I --> K{"Join Type?"} K --> L["inner/left/
right/outer"] style A fill:#d65d0e,stroke:#fe8019,color:#ebdbb2 style B fill:#d79921,stroke:#fabd2f,color:#282828 style C fill:#98971a,stroke:#b8bb26,color:#282828 style D fill:#98971a,stroke:#b8bb26,color:#282828 style E fill:#458588,stroke:#83a598,color:#ebdbb2 style F fill:#b16286,stroke:#d3869b,color:#ebdbb2 style G fill:#b16286,stroke:#d3869b,color:#ebdbb2 style H fill:#458588,stroke:#83a598,color:#ebdbb2 style I fill:#b16286,stroke:#d3869b,color:#ebdbb2 style J fill:#b16286,stroke:#d3869b,color:#ebdbb2 style K fill:#d79921,stroke:#fabd2f,color:#282828 style L fill:#cc241d,stroke:#fb4934,color:#ebdbb2

ตารางสรุปการเลือกใช้:

สถานการณ์ วิธีที่แนะนำ เหตุผล
รวมข้อมูลรายเดือนเป็นรายปี pd.concat(axis=0) โครงสร้างเหมือนกัน ต่อแนวตั้ง
เพิ่มคอลัมน์จากหลายแหล่ง pd.concat(axis=1) ต่อแนวนอน Index ตรงกัน
รวมลูกค้ากับคำสั่งซื้อ pd.merge(how='left') มี Key Column และต้องการเก็บลูกค้าทั้งหมด
เชื่อมข้อมูล 2 ตารางที่มี Key ตรงกัน pd.merge(how='inner') เฉพาะข้อมูลที่ตรงกันทั้งสองฝ่าย
ต่อตารางที่มี Index เป็น Key df.join() สะดวกและเร็วกว่าสำหรับ Index-based

สูตรสำคัญ (Formulas):

N result = { N left + N right สำหรับ concat(axis=0) min ( N left , N right ) สำหรับ inner join = N left สำหรับ left join max ( N left , N right ) สำหรับ outer join

โดยที่:

Best Practices สรุป:

ควรทำ (Do's):

ไม่ควรทำ (Don'ts):


เอกสารอ้างอิง (References)

  1. Pandas Official Documentation

  2. Performance Optimization

  3. Books & Tutorials

  4. Community Resources


หมายเหตุ: เอกสารนี้สร้างขึ้นเพื่อการศึกษาและอ้างอิง สามารถปรับปรุงและเพิ่มเติมตามความเหมาะสม ตัวอย่างโค้ดทั้งหมดทดสอบแล้วด้วย Pandas version 2.0+