ไฟล์ csv ไฟล์เก็บข้อมูลแบบเบสิคง่ายๆ ที่เราใช้แลกเปลี่ยนข้อมูลกันไปมานั้น ลองมาดูกันว่าเมื่อมันทำงานคู่กับโค้ด python แล้วมันจะเจ๋งแจ๋วสักแค่ไหนกัน
csv หรือ comma-separated values แน่นอนเป็นไฟล์ที่ใช้เก็บข้อมูลประเภทตาราง แต่ละ row (บรรทัด) แสดงถึงข้อมูลแต่ละรายการ โดยใช้เครื่องหมาย , (comma) เป็นตัวคั่นแต่ละคอลัมน์ออกจากกัน
มั่นใจว่าคุณผู้ป่านทุกท่านน่าจะเคยผ่านหูผ่านตา เคยใช้งานกับไฟล์ชนิดนี้มากันแล้วอย่างน้อยซักครั้ง ยิ่งท่านใดที่ใช้ python น่าจะเคย load data มันขึ้นมา process กันบ้างแหละเน๊อะ
ตัวอย่าง เราได้ทำการอ่านไฟล์ csv: Social Sentiment Data ที่ได้มาจาก Kaggle และอ่านมันออกมาทุกคอลัมน์เลย
import csv
with open('sentimentdataset.csv', newline='', encoding='utf-8') as csvfile:
reader = csv.reader(csvfile)
header = next(reader)
print("Columns:", header)
จะได้ผลลัพธ์ออกมาเป็นแบบนี้
Columns: ['', 'Unnamed: 0', 'Text', 'Sentiment', 'Timestamp', 'User', 'Platform', 'Hashtags', 'Retweets', 'Likes', 'Country', 'Year', 'Month', 'Day', 'Hour']
แล้ว Module csv ที่ว่าดีมันเป็นยังไง:
- มัน Detect ตัวคั่น delimiter จากไฟล์ได้
- ตรวจหาหัวตาราง Header Detection
- อ่านข้อมูลออกมาเป็น List
- ใส่ Quote ให้กับค่า Non-Numeric
1. มัน Detect ตัวคั่น delimiter จากไฟล์ได้
โดยปกติแล้วไฟล์ csv ถูกออกแบบมาใช้ , (comma) เป็นตัวคั่นของแต่ละคอลัมน์ แต่บางครั้งก็สามารถใช้เครื่องหมายอื่นได้ ไม่ว่าจะเป็น ; (semi-colon), \t (tab) หรือ | (pipe) ก็ได้
และหากเราใช้ method sniffer() จะตรวจหา delimiter ออกมาได้เลย โดยเราจะ sample data ออกมาไม่กี่แถวเพื่อดูว่าไฟล์นี้ใช้ตัวคั่นเป็นอะไรกันนะ
with open('sentimentdataset.csv', newline='', encoding='utf-8') as f:
sample = f.read(2048)
dialect = csv.Sniffer().sniff(sample, delimiters=[',',';','\t', '|'])
print(f"Detected delimiter: {repr(dialect.delimiter)}")
ในโค้ดด้านบนนี้ เราจะแสดงตัวอย่างจากข้อมูลออกมาประมาณ 2048 ตัวอักษรแรก แล้วสามารถเพิ่มตัวคั่นอื่นๆ ที่ต้องการตรวจจับได้ใน list ของ parameter delimiters=[',', ';', '\t', '|'] นี้ ซึ่งโค้ดด้านบนได้ผลลัพธ์คือ
Detected delimiter: ','
2. ตรวจหาหัวตาราง Header Detection
Module csv ไม่ใช่แค่ตรวจหาตัวคั่นได้เท่านั้น แต่ยังตรวจหาว่าไฟล์นี้ “มีคอลัมน์หัวตารางมั้ย?” ได้ตามโค้ดนี้เลย
has_header = csv.Sniffer().has_header(sample)
print("Header detected?" , has_header)
ได้ผลลัพธ์เป็น
Header detected? True
ห๊ะ! แค่่นี้เองเหรอ? (ตอนแรกหนุ่มก็ถามคำถามนี้เหมือนกัน) และก็ไปหาข้อมูลมาเพิ่มก็พบกรณีที่สามารถใช้ได้ก็คือ เราสามารถมันตรวจไฟล์ csv ที่ไม่มีโครงสร้างที่แน่นอน หรือไฟล์ที่เราไม่แน่ใจว่ามีแถวส่วนหัว (header row) อยู่หรือไม่
- ไฟล์ csv ไม่มีมาตรฐานที่ตายตัว บางไฟล์มี header บางไฟล์ไม่มี
- หากเราพยายามอ่านไฟล์ csv ที่ไม่มี header ออกมา หรือพยายามใช้แถวแรก (ที่เป็นข้อมูล) แทนที่จะเป็นชื่อคอลัมน์ อาจจะประมวลข้อมูลผิดพลาดได้
3. อ่านข้อมูลออกมาเป็น List
โดยปกติตามความเคยชินของแอดเองจะอ่านไฟล์ csv ออกมาเป็น Pandas Data Frame เลย ซึ่งมันทำให้ใช้การวนซ้ำ (iterate) ได้ยาก พอเป็น List แล้วง่ายกว่าเยอะเลย มาดูโค้ดกันเลย
with open('sentimentdataset.csv', newline='', encoding='utf-8') as f:
reader = csv.reader(f, dialect)
header = next(reader)
for i, row in enumerate(reader):
if i >= 1: break
print(row)
ได้ผลลัพธ์เป็น
['0', '0', ' Enjoying a beautiful day at the park! ', ' Positive ', '2023-01-15 12:30:00', ' User123 ', ' Twitter ', ' #Nature #Park ', '15.0', '30.0', ' USA ', '2023', '1', '15', '12']
โดย csv.reader การอ่านข้อมูลแต่ละ row จะคืนค่าเป็น List หากการทำงานของเราไม่จำเป็นต้องใช้ชื่อคอลัมน์ ก็สามารถเข้าถึงข้อมูลแต่ละฟิลด์ด้วย index ของมันแทน (เช่น row[0], row[1]) ดังตัวอย่างคือการดึงข้อมูลออกมาแสดงผล
with open('sentimentdataset.csv', newline='', encoding='utf-8') as f:
reader = csv.reader(f, dialect)
header = next(reader)
for i, row in enumerate(reader):
if i >= 5: break
print(f"ID: {row[0]}, TextComment: {row[2].strip()}, Sentiment: {row[3].strip()}")
ได้ผลลัพธ์เป็น
ID: 0, TextComment: Enjoying a beautiful day at the park!, Sentiment: Positive
ID: 1, TextComment: Traffic was terrible this morning., Sentiment: Negative
ID: 2, TextComment: Just finished an amazing workout! 💪, Sentiment: Positive
ID: 3, TextComment: Excited about the upcoming weekend getaway!, Sentiment: Positive
ID: 4, TextComment: Trying out a new recipe for dinner tonight., Sentiment: Neutral
อีกตัวอย่างถ้าเป็น list แล้วเราสามารถตรวจสอบเงื่อนไขง่ายได้ด้วย List Comprehension เช่น ต้องการอยากรู้ว่ามีคอมเมนต์ประเภทไหนบ้าง
with open('sentimentdataset.csv', newline='', encoding='utf-8') as f:
reader = csv.reader(f, dialect)
header = next(reader)
all_rows = list(reader) # อ่านข้อมูลทั้งหมดจาก reader เข้ามาเก็บใน list เดียว
res_pos = [1 if row[3].strip() == 'Positive' else 0 for row in all_rows]
res_neg = [1 if row[3].strip() == 'Negative' else 0 for row in all_rows]
res_neu = [1 if row[3].strip() == 'Neutral' else 0 for row in all_rows]
print(f"Total number of Positive comments is {sum(res_pos)}")
print(f"Total number of Negative comments is {sum(res_neg)}")
print(f"Total number of Neutral comments is {sum(res_neu)}")
ได้ผลลัพธ์เป็น
Total number of Positive comments is 45
Total number of Negative comments is 4
Total number of Neutral comments is 18
4. ใส่ Quote ให้กับค่า Non-Numeric
ในไฟล์ csv แต่ลัฟิลด์สามารถมีเครื่องหมาย , (comma), " " (double quotes) หรือผสมระหว่างข้อความและตัวเลขก็ได้ ทำให้เมื่อเราต้อง export file csv ออกไปนั้นต้องใส่เครื่องหมาย " " (double quotes) ครอบเอาไว้กันความคลาดเคลื่อน ผิดพลาดเมื่อเรานำไปไฟล์ไปเปิดใช้งานเครื่องอื่น
เพื่อให้แน่ใจว่าสิ่งใดก็ตามภายในนั้น (แม้แต่เครื่องหมาย comma หรือการเว้นบรรทัด) จะถูกถือว่าเป็นส่วนหนึ่งของค่า ไม่ใช่เป็นตัวคั่น
INPUT = 'sentimentdataset.csv'
OUTPUT = 'quoted_nonnum.csv'
with open(INPUT, newline='', encoding='utf-8') as fin, \
open(OUTPUT, 'w', newline='', encoding='utf-8') as fout:
reader = csv.DictReader(fin)
writer = csv.writer(fout, quoting=csv.QUOTE_NONNUMERIC)
writer.writerow(['Text', 'Likes'])
for i, row in enumerate(reader):
if i >= 5: break
writer.writerow([ row['Text'].strip() , float(row['Likes']) ])
ในโค้ดด้านบน เราเลือกคอลัมน์ Text และ Likes โดยใส่เครื่องหมายคำพูด " " (double quotes) ในค่าที่ไม่ใช่ตัวเลขทั้งหมด จากได้ผลลัพธ์เป็น:
"Text","Likes"
"Enjoying a beautiful day at the park!",30.0
"Traffic was terrible this morning.",10.0
"Just finished an amazing workout! 💪",40.0
"Excited about the upcoming weekend getaway!",15.0
"Trying out a new recipe for dinner tonight.",25.0
และถ้าต้องการใส่เครื่องหมายคำพูด " " (double quotes) ครอบทุกค่าเลยล่ะ
INPUT = 'Ref/sentimentdataset.csv'
OUTPUT = 'Ref/quoted_nonnum.csv'
with open(INPUT, newline='', encoding='utf-8') as fin, \
open(OUTPUT, 'w', newline='', encoding='utf-8') as fout:
reader = csv.DictReader(fin)
writer = csv.writer(fout, quoting=csv.QUOTE_ALL)
writer.writerow(['Text', 'Likes'])
for i, row in enumerate(reader):
if i >= 5: break
writer.writerow([ row['Text'].strip() , float(row['Likes']) ])
เปลี่ยน parameter เป็น quoting=csv.QUOTE_ALL จะได้ผลลัพธ์เป็น
"Text","Likes"
"Enjoying a beautiful day at the park!","30.0"
"Traffic was terrible this morning.","10.0"
"Just finished an amazing workout! 💪","40.0"
"Excited about the upcoming weekend getaway!","15.0"
"Trying out a new recipe for dinner tonight.","25.0"
แล้วมีประโยชน์ยังไงล่ะ
- เมื่อเราเปิดไฟล์ csv ในโปรแกรมสเปรดชีต เช่น Excel, Google Sheets, หรือโปรแกรมวิเคราะห์ข้อมูลบางชนิด โปรแกรมเหล่านี้จะพยายาม “เดา” ชนิดข้อมูลของแต่ละคอลัมน์
- ถ้าค่าตัวเลขถูกเขียนโดยไม่มีเครื่องหมายคำพูด (เช่น
123,45.67) โปรแกรมเหล่านี้มักจะตีความว่าเป็น ตัวเลข โดยอัตโนมัติ ซึ่งเหมาะสำหรับการคำนวณทางคณิตศาสตร์ - แต่ถ้าค่านั้นเป็น string (ข้อความ) เช่น “Product ID”, “Apple” หรือ “123 Main Street” ถูกเขียนโดยมีเครื่องหมายคำพูดคร่อม (เช่น
"Product ID","Apple","123 Main Street") โปรแกรมจะตีความว่าเป็น ข้อความ ทำให้ไม่สับสนระหว่างข้อความที่ดูเหมือนตัวเลขกับตัวเลขจริงๆ - สมมติคุณมีค่า
007ซึ่งควรเป็น string (เช่น รหัสสินค้า ที่บางครั้งขึ้นต้นด้วย 0) ถ้าบันทึกโดยไม่มีเครื่องหมายคำพูด โปรแกรมสเปรดชีตบางตัวอาจตีความว่าเป็นตัวเลขและแปลงเป็น7ซึ่งทำให้ข้อมูลผิดพลาด
เปรียบเทียบ quoting=csv.QUOTE_NONNUMERIC กับโหมดอื่น ๆ
quoting=csv.QUOTE_ALL: ใส่ double quotes คร่อม ทุกค่า ในไฟล์ csv ไม่ว่าจะเป็นตัวเลขหรือข้อความ ("123","Apple") ทำให้โปรแกรมตีความตัวเลขเป็นสตริงquoting=csv.QUOTE_MINIMAL(default): ใส่ double quotes คร่อมเฉพาะค่าที่ จำเป็นต้อง quote เท่านั้น เช่น มีตัวแบ่งคอลัมน์ (delimiter), เครื่องหมายคำพูด (quotechar) หรืออักขระขึ้นบรรทัดใหม่ (newline) อยู่ในค่าquoting=csv.QUOTE_NONE: ไม่ใส่เครื่องหมายคำพูดเลย แม้ว่าค่าจะมีตัวแบ่งคอลัมน์ก็ตาม ซึ่งมักใช้กับไฟล์ที่ข้อมูลไม่มีอักขระพิเศษ หรือเมื่อมีescapecharสำหรับ escape อักขระพิเศษแทน
การดีลกับไฟล์ csv มีหลายวิธีการ แอดจึงขอยกมานำเสนอเพียงเท่านั้นก่อน ยังไงก็ตาม มีสิ่งที่น่าสนใจหลายอย่างที่สามารถทำได้ด้วยโมดูลนี้ หวังว่าข้อมูลนี้คงมีประโยชน์กับคุณผู้อ่านนะครับ






