8. การจัดการข้อมูลอนุกรมเวลา (Time Series)

การวิเคราะห์ข้อมูลอนุกรมเวลา (Time Series) เป็นหนึ่งในจุดแข็งที่สำคัญของ Pandas โดยเฉพาะในการทำงานกับข้อมูลทางการเงิน ข้อมูลเซ็นเซอร์ ข้อมูลสภาพอากาศ หรือข้อมูลที่มีการบันทึกตามช่วงเวลา Pandas มี ฟังก์ชันพิเศษ (Built-in Functions) ที่ออกแบบมาโดยเฉพาะเพื่อจัดการกับวันที่และเวลาอย่างมีประสิทธิภาพ

%%{init: {'theme':'base', 'themeVariables': { 'primaryColor':'#b8bb26','primaryTextColor':'#282828','primaryBorderColor':'#fabd2f','lineColor':'#83a598','secondaryColor':'#d3869b','tertiaryColor':'#8ec07c','background':'#282828','mainBkg':'#3c3836','textColor':'#ebdbb2'}}}%%
graph TB
    A["การจัดการข้อมูลอนุกรมเวลา
(Time Series Management)"] A --> B["การแปลงข้อมูล
(Data Conversion)"] A --> C["การจัดการดัชนี
(Index Management)"] A --> D["การสุ่มตัวอย่าง
(Resampling)"] A --> E["การคำนวณเคลื่อนที่
(Rolling Calculations)"] B --> B1["pd.to_datetime()"] B --> B2["DateOffset"] C --> C1["set_index()"] C --> C2["Date Range"] D --> D1["Upsampling"] D --> D2["Downsampling"] E --> E1["Rolling Mean"] E --> E2["Expanding"] style A fill:#458588,stroke:#ebdbb2,color:#ebdbb2 style B fill:#689d6a,stroke:#ebdbb2,color:#282828 style C fill:#689d6a,stroke:#ebdbb2,color:#282828 style D fill:#689d6a,stroke:#ebdbb2,color:#282828 style E fill:#689d6a,stroke:#ebdbb2,color:#282828

8.1 การแปลงเป็น Datetime (Converting to Datetime)

ข้อมูลวันที่และเวลามักจะถูกเก็บในรูปแบบของ String (ข้อความ) เช่น "2024-01-15" หรือ "15/01/2024" ซึ่งไม่สามารถใช้ในการคำนวณทางคณิตศาสตร์ได้ Pandas จึงมีฟังก์ชัน pd.to_datetime() ที่ช่วยแปลงข้อความเหล่านี้ให้เป็น Datetime Object ที่สามารถคำนวณและจัดการได้

8.1.1 รูปแบบพื้นฐานของ pd.to_datetime()

import pandas as pd
import numpy as np

def convert_to_datetime_basic():
    """
    แสดงวิธีการแปลงข้อมูลแบบต่างๆ ให้เป็น Datetime
    
    Returns:
        DataFrame: ตารางแสดงข้อมูลก่อนและหลังการแปลง
    """
    # สร้างข้อมูลตัวอย่าง
    data = {
        'date_str': ['2024-01-15', '2024-02-20', '2024-03-10'],
        'date_thai': ['15/01/2024', '20/02/2024', '10/03/2024'],
        'timestamp': [1705276800, 1708387200, 1710028800]
    }
    df = pd.DataFrame(data)
    
    # แปลงรูปแบบ ISO (YYYY-MM-DD)
    df['datetime_iso'] = pd.to_datetime(df['date_str'])
    
    # แปลงรูปแบบ DD/MM/YYYY ต้องระบุ format
    df['datetime_thai'] = pd.to_datetime(df['date_thai'], format='%d/%m/%Y')
    
    # แปลงจาก Unix timestamp (วินาที)
    df['datetime_unix'] = pd.to_datetime(df['timestamp'], unit='s')
    
    return df

# ตัวอย่างการใช้งาน
result = convert_to_datetime_basic()
print(result)
print(f"\nชนิดข้อมูลของ datetime_iso: {result['datetime_iso'].dtype}")

ผลลัพธ์:

      date_str date_thai   timestamp datetime_iso datetime_thai        datetime_unix
0  2024-01-15 15/01/2024  1705276800   2024-01-15    2024-01-15  2024-01-15 00:00:00
1  2024-02-20 20/02/2024  1708387200   2024-02-20    2024-02-20  2024-02-20 00:00:00
2  2024-03-10 10/03/2024  1710028800   2024-03-10    2024-03-10  2024-03-10 00:00:00

ชนิดข้อมูลของ datetime_iso: datetime64[ns]

8.1.2 รูปแบบ Format Code ที่ใช้บ่อย

Format Code ความหมาย ตัวอย่าง
%Y ปี 4 หลัก 2024
%y ปี 2 หลัก 24
%m เดือนเป็นตัวเลข 01, 12
%B ชื่อเดือนเต็ม January
%b ชื่อเดือนย่อ Jan
%d วันที่ 01, 31
%H ชั่วโมง (24 ชม.) 00-23
%M นาที 00-59
%S วินาที 00-59

8.1.3 การจัดการข้อมูลที่มีปัญหา (Error Handling)

def handle_datetime_errors():
    """
    แสดงวิธีจัดการกับข้อมูลวันที่ที่มีปัญหา
    
    Returns:
        DataFrame: ข้อมูลที่ผ่านการจัดการข้อผิดพลาด
    """
    # ข้อมูลที่มีปัญหาปะปน
    problematic_dates = ['2024-01-15', 'invalid_date', '2024-02-30', '2024-03-10']
    
    # errors='coerce' จะแปลงค่าที่ผิดเป็น NaT (Not a Time)
    dates_coerce = pd.to_datetime(problematic_dates, errors='coerce')
    
    # errors='ignore' จะคงค่าเดิมไว้หากแปลงไม่ได้
    dates_ignore = pd.to_datetime(problematic_dates, errors='ignore')
    
    df = pd.DataFrame({
        'original': problematic_dates,
        'coerce': dates_coerce,
        'ignore': dates_ignore
    })
    
    return df

# ตัวอย่างการใช้งาน
result = handle_datetime_errors()
print(result)

8.1.4 การดึงส่วนประกอบของวันที่ (Date Components)

def extract_date_components():
    """
    แสดงวิธีดึงส่วนประกอบต่างๆ ของวันที่
    
    Returns:
        DataFrame: ตารางแสดงส่วนประกอบต่างๆ
    """
    # สร้างข้อมูลตัวอย่าง
    dates = pd.date_range('2024-01-01', periods=5, freq='D')
    df = pd.DataFrame({'date': dates})
    
    # ดึงส่วนประกอบต่างๆ
    df['year'] = df['date'].dt.year          # ปี
    df['month'] = df['date'].dt.month        # เดือน
    df['day'] = df['date'].dt.day            # วันที่
    df['dayofweek'] = df['date'].dt.dayofweek  # วันในสัปดาห์ (0=จันทร์)
    df['day_name'] = df['date'].dt.day_name()  # ชื่อวัน
    df['quarter'] = df['date'].dt.quarter    # ไตรมาส
    df['is_month_end'] = df['date'].dt.is_month_end  # เป็นวันสุดท้ายของเดือนหรือไม่
    
    return df

# ตัวอย่างการใช้งาน
result = extract_date_components()
print(result)

8.2 การตั้งค่า Date เป็น Index (Setting Date as Index)

การตั้งค่าคอลัมน์วันที่เป็น Index ของ DataFrame จะทำให้การทำงานกับข้อมูลอนุกรมเวลามีประสิทธิภาพมากขึ้น โดยเฉพาะการกรองข้อมูลตามช่วงเวลา การ Plot กราฟ และการ Resample

8.2.1 การสร้าง DatetimeIndex

def create_datetime_index():
    """
    แสดงวิธีสร้างและใช้งาน DatetimeIndex
    
    Returns:
        DataFrame: DataFrame ที่มี DatetimeIndex
    """
    # วิธีที่ 1: ใช้ pd.date_range()
    dates = pd.date_range(start='2024-01-01', end='2024-01-10', freq='D')
    
    # สร้าง DataFrame พร้อม Index เป็นวันที่
    df = pd.DataFrame({
        'sales': np.random.randint(100, 1000, size=10),
        'customers': np.random.randint(10, 100, size=10)
    }, index=dates)
    
    print("DataFrame with DatetimeIndex:")
    print(df.head())
    print(f"\nIndex Type: {type(df.index)}")
    
    return df

# ตัวอย่างการใช้งาน
df = create_datetime_index()

8.2.2 การแปลง Column เป็น Index

def convert_column_to_index():
    """
    แสดงวิธีแปลง Column วันที่ให้เป็น Index
    
    Returns:
        DataFrame: DataFrame ที่แปลง Column เป็น Index แล้ว
    """
    # สร้างข้อมูลที่มี Column วันที่
    data = {
        'date': pd.date_range('2024-01-01', periods=10, freq='D'),
        'temperature': np.random.uniform(20, 35, 10).round(1),
        'humidity': np.random.uniform(40, 90, 10).round(1)
    }
    df = pd.DataFrame(data)
    
    print("Before setting index:")
    print(df.head())
    
    # แปลง Column 'date' เป็น Index
    df = df.set_index('date')
    
    print("\nAfter setting index:")
    print(df.head())
    
    return df

# ตัวอย่างการใช้งาน
df = convert_column_to_index()

8.2.3 การกรองข้อมูลด้วย Date Range

%%{init: {'theme':'base', 'themeVariables': { 'primaryColor':'#b8bb26','primaryTextColor':'#282828','primaryBorderColor':'#fabd2f','lineColor':'#83a598','secondaryColor':'#d3869b','tertiaryColor':'#8ec07c','background':'#282828','mainBkg':'#3c3836','textColor':'#ebdbb2'}}}%%
graph LR
    A["DataFrame
ทั้งหมด"] --> B["การกรองด้วย
Slice Notation"] A --> C["การกรองด้วย
loc[]"] A --> D["การกรองด้วย
Boolean Mask"] B --> B1["df['2024-01']
ทั้งเดือน"] B --> B2["df['2024-01-01':'2024-01-15']
ช่วงวันที่"] C --> C1["df.loc['2024-01-01']
วันเดียว"] D --> D1["df[df.index.month == 1]
เงื่อนไขซับซ้อน"] style A fill:#458588,stroke:#ebdbb2,color:#ebdbb2 style B fill:#b8bb26,stroke:#282828,color:#282828 style C fill:#b8bb26,stroke:#282828,color:#282828 style D fill:#b8bb26,stroke:#282828,color:#282828
def filter_by_date_range():
    """
    แสดงวิธีกรองข้อมูลด้วยช่วงวันที่ต่างๆ
    
    Returns:
        dict: Dictionary ที่เก็บผลลัพธ์การกรองแต่ละแบบ
    """
    # สร้างข้อมูลตัวอย่าง
    dates = pd.date_range('2024-01-01', periods=60, freq='D')
    df = pd.DataFrame({
        'value': np.random.randn(60).cumsum()
    }, index=dates)
    
    results = {}
    
    # วิธีที่ 1: กรองทั้งเดือน
    results['january'] = df['2024-01']
    
    # วิธีที่ 2: กรองช่วงวันที่เฉพาะ
    results['mid_month'] = df['2024-01-10':'2024-01-20']
    
    # วิธีที่ 3: ใช้ loc[] สำหรับวันเดียว
    results['specific_day'] = df.loc['2024-01-15']
    
    # วิธีที่ 4: กรองด้วยเงื่อนไขซับซ้อน (วันจันทร์ทั้งหมด)
    results['mondays'] = df[df.index.dayofweek == 0]
    
    # วิธีที่ 5: กรองสัปดาห์ที่ 3 ของทุกเดือน
    results['week_3'] = df[df.index.day.isin(range(15, 22))]
    
    return results, df

# ตัวอย่างการใช้งาน
results, original_df = filter_by_date_range()
print("Original DataFrame shape:", original_df.shape)
print("\nJanuary data shape:", results['january'].shape)
print("\nMid-month data:")
print(results['mid_month'].head())

8.2.4 Frequency Codes สำหรับ pd.date_range()

Frequency ความหมาย ตัวอย่าง
D รายวัน (Day) freq='D'
W รายสัปดาห์ (Week) freq='W'
M วันสุดท้ายของเดือน (Month End) freq='M'
MS วันแรกของเดือน (Month Start) freq='MS'
Q วันสุดท้ายของไตรมาส (Quarter End) freq='Q'
Y วันสุดท้ายของปี (Year End) freq='Y'
H รายชั่วโมง (Hour) freq='H'
T หรือ min รายนาที (Minute) freq='T'
S รายวินาที (Second) freq='S'
B วันทำการ (Business Day) freq='B'

8.3 การ Resample ข้อมูล (Resampling Data)

Resampling คือการเปลี่ยนความถี่ของข้อมูลอนุกรมเวลา แบ่งออกเป็น 2 ประเภทหลัก:

  1. Downsampling: ลดความถี่ข้อมูล (เช่น จากรายชั่วโมงเป็นรายวัน)
  2. Upsampling: เพิ่มความถี่ข้อมูล (เช่น จากรายวันเป็นรายชั่วโมง)

8.3.1 การทำ Downsampling

การรวมข้อมูลความถี่สูงเป็นความถี่ต่ำต้องใช้ ฟังก์ชันการรวบรวม (Aggregation Function) เช่น sum, mean, max, min

X daily = 1 n i=1 n X hourly,i

โดยที่:

def demonstrate_downsampling():
    """
    แสดงวิธีทำ Downsampling ด้วยฟังก์ชันต่างๆ
    
    Returns:
        dict: Dictionary ที่เก็บผลลัพธ์การ Resample แต่ละแบบ
    """
    # สร้างข้อมูลรายชั่วโมง
    dates = pd.date_range('2024-01-01', periods=24*7, freq='H')  # 1 สัปดาห์
    df = pd.DataFrame({
        'temperature': np.random.uniform(15, 30, len(dates)),
        'sales': np.random.randint(0, 100, len(dates)),
        'visitors': np.random.randint(10, 200, len(dates))
    }, index=dates)
    
    results = {}
    
    # Resample เป็นรายวัน - ใช้ค่าเฉลี่ย
    results['daily_mean'] = df.resample('D').mean()
    
    # Resample เป็นรายวัน - ใช้ผลรวม
    results['daily_sum'] = df.resample('D').sum()
    
    # Resample เป็นรายวัน - ใช้หลายฟังก์ชันพร้อมกัน
    results['daily_agg'] = df.resample('D').agg({
        'temperature': ['mean', 'max', 'min'],
        'sales': 'sum',
        'visitors': 'sum'
    })
    
    # Resample เป็นราย 6 ชั่วโมง
    results['6h'] = df.resample('6H').mean()
    
    # Resample เป็นรายสัปดาห์
    results['weekly'] = df.resample('W').agg({
        'temperature': 'mean',
        'sales': 'sum',
        'visitors': 'sum'
    })
    
    print("Original data shape:", df.shape)
    print("\nDaily mean shape:", results['daily_mean'].shape)
    print("\nDaily aggregation:")
    print(results['daily_agg'].head())
    
    return results, df

# ตัวอย่างการใช้งาน
results, original_df = demonstrate_downsampling()

8.3.2 การทำ Upsampling

การเพิ่มความถี่ข้อมูลจะทำให้เกิด ช่องว่าง (Missing Values) ที่ต้องจัดการด้วยวิธีการต่างๆ เช่น Forward Fill, Backward Fill, หรือ Interpolation

def demonstrate_upsampling():
    """
    แสดงวิธีทำ Upsampling และการจัดการค่าว่าง
    
    Returns:
        DataFrame: DataFrame ที่ Upsample แล้ว
    """
    # สร้างข้อมูลรายวัน
    dates = pd.date_range('2024-01-01', periods=7, freq='D')
    df = pd.DataFrame({
        'price': [100, 105, 103, 108, 106, 110, 112]
    }, index=dates)
    
    print("Original daily data:")
    print(df)
    
    # Upsample เป็นรายชั่วโมง
    hourly = df.resample('H').asfreq()
    
    print("\n\nAfter upsampling to hourly (with NaN):")
    print(hourly.head(25))
    
    # จัดการค่าว่างด้วย Forward Fill
    hourly_ffill = df.resample('H').ffill()
    
    print("\n\nWith Forward Fill:")
    print(hourly_ffill.head(25))
    
    # จัดการค่าว่างด้วย Interpolation (Linear)
    hourly_interpolate = df.resample('H').interpolate(method='linear')
    
    print("\n\nWith Linear Interpolation:")
    print(hourly_interpolate.head(25))
    
    return hourly_ffill, hourly_interpolate

# ตัวอย่างการใช้งาน
ffill_df, interp_df = demonstrate_upsampling()

8.3.3 วิธีการจัดการค่าว่างใน Upsampling

Method ความหมาย เหมาะกับ
ffill() Forward Fill - ใช้ค่าก่อนหน้าไปเติม ราคาหุ้น, สถานะ On/Off
bfill() Backward Fill - ใช้ค่าถัดไปมาเติม การพยากรณ์ย้อนหลัง
interpolate() คำนวณค่ากลางระหว่างจุด อุณหภูมิ, ข้อมูลต่อเนื่อง
fillna(value) เติมด้วยค่าคงที่ ใช้ค่าเฉลี่ยหรือ 0

8.3.4 การใช้ Offset Aliases ขั้นสูง

def advanced_resampling():
    """
    แสดงเทคนิค Resample ขั้นสูง
    
    Returns:
        dict: ผลลัพธ์การ Resample แบบต่างๆ
    """
    # สร้างข้อมูล Intraday (ภายในวัน)
    dates = pd.date_range('2024-01-01 09:00', periods=390, freq='T')  # ทุก 1 นาที
    df = pd.DataFrame({
        'stock_price': 100 + np.random.randn(390).cumsum() * 0.1
    }, index=dates)
    
    results = {}
    
    # Resample ทุก 5 นาที - แบบ OHLC (Open, High, Low, Close)
    results['5min_ohlc'] = df.resample('5T').ohlc()
    
    # Resample ทุก 15 นาที - คำนวณหลายค่า
    results['15min'] = df.resample('15T').agg({
        'stock_price': ['first', 'last', 'max', 'min', 'mean']
    })
    
    # Resample ทุก 30 นาที - เริ่มที่นาทีที่ 15
    results['30min_offset'] = df.resample('30T', offset='15T').mean()
    
    # Resample รายชั่วโมง - ใช้ label='right' (แสดงเวลาสิ้นสุด)
    results['hourly_right'] = df.resample('H', label='right').mean()
    
    print("5-minute OHLC:")
    print(results['5min_ohlc'].head())
    
    return results, df

# ตัวอย่างการใช้งาน
results, stock_df = advanced_resampling()

8.4 การคำนวณแบบ Rolling (Rolling Calculations)

Rolling Window เป็นเทคนิคที่ใช้คำนวณค่าสถิติจากกลุ่มข้อมูลที่เลื่อนไปเรื่อยๆ (Sliding Window) มักใช้ในการหา Moving Average, Volatility, และตัวชี้วัดทางเทคนิคต่างๆ

SMA ( t , n ) = 1 n i=0 n1 P ti

โดยที่:

8.4.1 พื้นฐาน Rolling Window

%%{init: {'theme':'base', 'themeVariables': { 'primaryColor':'#b8bb26','primaryTextColor':'#282828','primaryBorderColor':'#fabd2f','lineColor':'#83a598','secondaryColor':'#d3869b','tertiaryColor':'#8ec07c','background':'#282828','mainBkg':'#3c3836','textColor':'#ebdbb2'}}}%%
sequenceDiagram
    participant D as Data Points
    participant W1 as Window 1
    participant W2 as Window 2
    participant W3 as Window 3
    participant R as Result
    
    D->>W1: [1, 2, 3, 4, 5]
First 5 points W1->>R: Mean = 3.0 D->>W2: [2, 3, 4, 5, 6]
Slide +1 W2->>R: Mean = 4.0 D->>W3: [3, 4, 5, 6, 7]
Slide +1 W3->>R: Mean = 5.0 Note over D,R: Rolling window size = 5
def basic_rolling_calculations():
    """
    แสดงการคำนวณแบบ Rolling พื้นฐาน
    
    Returns:
        DataFrame: DataFrame ที่มีการคำนวณ Rolling
    """
    # สร้างข้อมูลตัวอย่าง - ราคาหุ้น
    np.random.seed(42)
    dates = pd.date_range('2024-01-01', periods=100, freq='D')
    df = pd.DataFrame({
        'price': 100 + np.random.randn(100).cumsum()
    }, index=dates)
    
    # คำนวณ Moving Average หลายช่วงเวลา
    df['MA5'] = df['price'].rolling(window=5).mean()    # 5 วัน
    df['MA10'] = df['price'].rolling(window=10).mean()  # 10 วัน
    df['MA20'] = df['price'].rolling(window=20).mean()  # 20 วัน
    
    # คำนวณ Rolling Standard Deviation (Volatility)
    df['volatility'] = df['price'].rolling(window=20).std()
    
    # คำนวณ Rolling Min/Max
    df['rolling_max_10'] = df['price'].rolling(window=10).max()
    df['rolling_min_10'] = df['price'].rolling(window=10).min()
    
    print("DataFrame with Rolling Calculations:")
    print(df.tail(10))
    
    return df

# ตัวอย่างการใช้งาน
df_rolling = basic_rolling_calculations()

8.4.2 การปรับแต่ง Rolling Window

def custom_rolling_windows():
    """
    แสดงวิธีปรับแต่ง Rolling Window แบบต่างๆ
    
    Returns:
        DataFrame: DataFrame ที่มีการปรับแต่ง Rolling
    """
    # สร้างข้อมูล
    dates = pd.date_range('2024-01-01', periods=50, freq='D')
    df = pd.DataFrame({
        'value': np.random.randn(50).cumsum() + 100
    }, index=dates)
    
    # min_periods: จำนวนค่าขั้นต่ำที่ต้องมีในการคำนวณ
    df['MA7_default'] = df['value'].rolling(window=7).mean()  # มี NaN 6 ตัวแรก
    df['MA7_min3'] = df['value'].rolling(window=7, min_periods=3).mean()  # มี NaN 2 ตัวแรก
    
    # center: จัดค่ากลางให้อยู่ตรงกลางหน้าต่าง
    df['MA7_center'] = df['value'].rolling(window=7, center=True).mean()
    
    # ใช้ฟังก์ชันหลายตัวพร้อมกัน
    df_agg = df['value'].rolling(window=10).agg(['mean', 'std', 'min', 'max'])
    
    print("Rolling with different settings:")
    print(df.head(10))
    print("\nMultiple aggregations:")
    print(df_agg.head(15))
    
    return df, df_agg

# ตัวอย่างการใช้งาน
df_custom, df_agg = custom_rolling_windows()

8.4.3 การใช้ Rolling กับ Custom Function

def rolling_with_custom_function():
    """
    แสดงการใช้ Rolling กับฟังก์ชันที่กำหนดเอง
    
    Returns:
        DataFrame: DataFrame ที่ใช้ Custom Function
    """
    # สร้างข้อมูลราคาหุ้น
    dates = pd.date_range('2024-01-01', periods=100, freq='D')
    df = pd.DataFrame({
        'open': 100 + np.random.randn(100).cumsum(),
        'high': 102 + np.random.randn(100).cumsum(),
        'low': 98 + np.random.randn(100).cumsum(),
        'close': 100 + np.random.randn(100).cumsum(),
        'volume': np.random.randint(1000000, 10000000, 100)
    }, index=dates)
    
    # ฟังก์ชันคำนวณ RSI (Relative Strength Index)
    def calculate_rsi(prices, period=14):
        """
        คำนวณ RSI
        
        Args:
            prices: Series ของราคา
            period: จำนวนวันที่ใช้คำนวณ
            
        Returns:
            float: ค่า RSI
        """
        if len(prices) < period:
            return np.nan
        
        delta = prices.diff()
        gain = (delta.where(delta > 0, 0)).mean()
        loss = (-delta.where(delta < 0, 0)).mean()
        
        if loss == 0:
            return 100
        
        rs = gain / loss
        rsi = 100 - (100 / (1 + rs))
        return rsi
    
    # ใช้ Custom Function กับ Rolling
    df['RSI_14'] = df['close'].rolling(window=14).apply(
        lambda x: calculate_rsi(x), raw=False
    )
    
    # ฟังก์ชันคำนวณ Price Range
    def price_range(window):
        """คำนวณช่วงราคาในหน้าต่าง"""
        return window.max() - window.min()
    
    df['price_range_10'] = df['close'].rolling(window=10).apply(price_range)
    
    # คำนวณ Volume-Weighted Average Price (VWAP)
    df['typical_price'] = (df['high'] + df['low'] + df['close']) / 3
    df['vwap_10'] = (
        (df['typical_price'] * df['volume']).rolling(window=10).sum() /
        df['volume'].rolling(window=10).sum()
    )
    
    print("Custom Rolling Calculations:")
    print(df[['close', 'RSI_14', 'price_range_10', 'vwap_10']].tail(10))
    
    return df

# ตัวอย่างการใช้งาน
df_custom = rolling_with_custom_function()

8.5 Expanding และ ExponentialWeightedMoving

นอกจาก Rolling Window แล้ว Pandas ยังมีเทคนิคการคำนวณแบบ Expanding (ขยายหน้าต่าง) และ Exponentially Weighted Moving (ถ่วงน้ำหนักแบบเอ็กซ์โพเนนเชียล)

8.5.1 Expanding Window

Expanding จะคำนวณค่าสถิติโดยรวมข้อมูลทั้งหมดตั้งแต่เริ่มต้นจนถึงจุดปัจจุบัน

Expanding Mean ( t ) = 1 t i=1 t x i

โดยที่:

def expanding_calculations():
    """
    แสดงการใช้ Expanding Window
    
    Returns:
        DataFrame: DataFrame ที่มีการคำนวณ Expanding
    """
    # สร้างข้อมูลยอดขาย
    dates = pd.date_range('2024-01-01', periods=30, freq='D')
    df = pd.DataFrame({
        'daily_sales': np.random.randint(1000, 5000, 30)
    }, index=dates)
    
    # คำนวณ Cumulative (สะสม)
    df['cumulative_sales'] = df['daily_sales'].expanding().sum()
    
    # คำนวณค่าเฉลี่ยสะสม
    df['cumulative_avg'] = df['daily_sales'].expanding().mean()
    
    # คำนวณค่าสูงสุดสะสม
    df['cumulative_max'] = df['daily_sales'].expanding().max()
    
    # คำนวณ Standard Deviation สะสม
    df['cumulative_std'] = df['daily_sales'].expanding().std()
    
    print("Expanding Calculations:")
    print(df.head(15))
    
    return df

# ตัวอย่างการใช้งาน
df_expanding = expanding_calculations()

8.5.2 Exponentially Weighted Moving (EWM)

EWM ให้น้ำหนักกับข้อมูลล่าสุดมากกว่าข้อมูลเก่า โดยใช้ Smoothing Factor (α)

EWM ( t ) = α · x t + ( 1 α ) · EWM ( t 1 )

โดยที่:

def exponential_weighted_calculations():
    """
    แสดงการใช้ Exponentially Weighted Moving
    
    Returns:
        DataFrame: DataFrame ที่มีการคำนวณ EWM
    """
    # สร้างข้อมูลราคาที่มีความผันผวน
    dates = pd.date_range('2024-01-01', periods=100, freq='D')
    df = pd.DataFrame({
        'price': 100 + np.random.randn(100).cumsum()
    }, index=dates)
    
    # EWM ด้วย span (จำนวนวัน)
    df['EMA_12'] = df['price'].ewm(span=12, adjust=False).mean()
    df['EMA_26'] = df['price'].ewm(span=26, adjust=False).mean()
    
    # คำนวณ MACD (Moving Average Convergence Divergence)
    df['MACD'] = df['EMA_12'] - df['EMA_26']
    df['Signal'] = df['MACD'].ewm(span=9, adjust=False).mean()
    df['Histogram'] = df['MACD'] - df['Signal']
    
    # EWM ด้วย alpha (0-1)
    df['EMA_alpha'] = df['price'].ewm(alpha=0.3, adjust=False).mean()
    
    # EWM Standard Deviation
    df['EMA_std'] = df['price'].ewm(span=20, adjust=False).std()
    
    # Bollinger Bands
    df['BB_middle'] = df['price'].ewm(span=20, adjust=False).mean()
    df['BB_upper'] = df['BB_middle'] + (2 * df['EMA_std'])
    df['BB_lower'] = df['BB_middle'] - (2 * df['EMA_std'])
    
    print("Exponential Weighted Moving:")
    print(df[['price', 'EMA_12', 'EMA_26', 'MACD', 'Signal']].tail(10))
    
    return df

# ตัวอย่างการใช้งาน
df_ewm = exponential_weighted_calculations()

8.5.3 เปรียบเทียบ Rolling vs Expanding vs EWM

คุณสมบัติ Rolling Expanding EWM
ขนาดหน้าต่าง คงที่ ขยายตามเวลา ถ่วงน้ำหนัก
ข้อมูลเก่า หลุดออกจากหน้าต่าง รวมทั้งหมด ลดน้ำหนักลง
ความไวต่อการเปลี่ยนแปลง ปานกลาง ต่ำ สูง
การใช้งาน Moving Average Cumulative Stats Trend Following
ความซับซ้อน ต่ำ ต่ำ กลาง

8.6 Time Zones และการจัดการเวลาสากล

เมื่อทำงานกับข้อมูลจากหลายประเทศหรือข้อมูลการเงินระหว่างประเทศ การจัดการ Time Zone เป็นเรื่องสำคัญมาก

8.6.1 การทำงานกับ Time Zones

def working_with_timezones():
    """
    แสดงวิธีจัดการ Time Zones
    
    Returns:
        DataFrame: DataFrame ที่มี Time Zone ต่างๆ
    """
    # สร้างข้อมูลแบบ naive (ไม่มี timezone)
    dates_naive = pd.date_range('2024-01-01 09:00', periods=5, freq='H')
    df = pd.DataFrame({
        'value': [100, 102, 101, 103, 105]
    }, index=dates_naive)
    
    print("Naive datetime (no timezone):")
    print(df)
    print(f"Timezone: {df.index.tz}")
    
    # เพิ่ม timezone (localize)
    df_bangkok = df.copy()
    df_bangkok.index = df_bangkok.index.tz_localize('Asia/Bangkok')
    
    print("\n\nWith Bangkok timezone:")
    print(df_bangkok)
    print(f"Timezone: {df_bangkok.index.tz}")
    
    # แปลงเป็น timezone อื่น (convert)
    df_ny = df_bangkok.copy()
    df_ny.index = df_ny.index.tz_convert('America/New_York')
    
    print("\n\nConverted to New York timezone:")
    print(df_ny)
    
    # สร้างข้อมูลพร้อม timezone ตั้งแต่แรก
    dates_utc = pd.date_range(
        '2024-01-01 00:00', 
        periods=5, 
        freq='H', 
        tz='UTC'
    )
    df_utc = pd.DataFrame({
        'price': [100, 102, 101, 103, 105]
    }, index=dates_utc)
    
    print("\n\nCreated with UTC timezone:")
    print(df_utc)
    
    return df_bangkok, df_ny, df_utc

# ตัวอย่างการใช้งาน
df_bkk, df_ny, df_utc = working_with_timezones()

8.6.2 Time Zone ที่ใช้บ่อย

Time Zone String ตำแหน่ง UTC Offset
UTC สากล +00:00
Asia/Bangkok กรุงเทพ +07:00
Asia/Tokyo โตเกียว +09:00
Asia/Shanghai เซี่ยงไฮ้ +08:00
Europe/London ลอนดอน +00:00/+01:00
America/New_York นิวยอร์ก -05:00/-04:00
US/Pacific แปซิฟิก -08:00/-07:00

สรุป (Summary)

การจัดการข้อมูลอนุกรมเวลาด้วย Pandas เป็นทักษะที่สำคัญมากในการวิเคราะห์ข้อมูล โดยเฉพาะในด้านการเงิน IoT และข้อมูลที่มีการบันทึกตามเวลา จุดสำคัญที่ต้องจำ:

  1. การแปลงข้อมูล: ใช้ pd.to_datetime() เพื่อแปลง String เป็น Datetime object ที่สามารถคำนวณได้
  2. DatetimeIndex: การตั้งค่าคอลัมน์วันที่เป็น Index จะทำให้การทำงานกับข้อมูลอนุกรมเวลาสะดวกและรวดเร็วขึ้น
  3. Resampling: เปลี่ยนความถี่ข้อมูลได้ทั้ง Downsampling (ลดความถี่) และ Upsampling (เพิ่มความถี่)
  4. Rolling Calculations: ใช้หาค่าสถิติแบบเคลื่อนที่ เช่น Moving Average ที่นิยมใช้ในการวิเคราะห์เทรนด์
  5. Expanding & EWM: เทคนิคเพิ่มเติมสำหรับการคำนวณแบบสะสมและถ่วงน้ำหนัก
  6. Time Zones: การจัดการเวลาสากลสำหรับข้อมูลข้ามประเทศ

การเชี่ยวชาญเรื่องเหล่านี้จะช่วยให้คุณสามารถวิเคราะห์ข้อมูลอนุกรมเวลาได้อย่างมีประสิทธิภาพและแม่นยำ


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

  1. Pandas Official Documentation - Time Series / Date functionality

  2. Python datetime module

  3. McKinney, W. (2022). Python for Data Analysis, 3rd Edition. O'Reilly Media.

  4. VanderPlas, J. (2016). Python Data Science Handbook. O'Reilly Media.

  5. Pytz - World Timezone Definitions

  6. Technical Analysis Library in Python (ta-lib)