Python Pandas เทียบเท่าใน JavaScript


96

ด้วยตัวอย่าง CSV นี้:

   Source,col1,col2,col3
   foo,1,2,3
   bar,3,4,5

วิธีมาตรฐานที่ฉันใช้ Pandas คือ:

  1. แยกวิเคราะห์ CSV

  2. เลือกคอลัมน์ลงในกรอบข้อมูล ( col1และcol3)

  3. ประมวลผลคอลัมน์ (เช่นประเมินค่าของ col1และcol3)

มีไลบรารี JavaScript ที่เหมือนกับ Pandas หรือไม่?


6
แจ้งให้เราทราบว่าคุณจะจบลงด้วยอะไร นี่เป็นคำถามสำคัญสำหรับพวกเราหลายคน
Ahmed Fasih

คำตอบ:


133

ทุกคำตอบคือดี หวังว่าคำตอบของฉันจะครอบคลุม (เช่นพยายามแสดงรายการตัวเลือกทั้งหมด ) ฉันหวังว่าจะกลับมาและแก้ไขคำตอบนี้โดยใช้เกณฑ์ใด ๆ เพื่อช่วยในการตัดสินใจ

d3ผมหวังว่าทุกคนที่มาที่นี่เป็นที่คุ้นเคยกับ d3เป็น "มีด swiss army" ที่มีประโยชน์มากสำหรับการจัดการข้อมูลใน Javascript เช่นpandasมีประโยชน์สำหรับ Python คุณอาจเห็นว่าd3ใช้บ่อยเช่นpandasแม้ว่าd3จะไม่ใช่การแทนที่ DataFrame / Pandas อย่างแน่นอน (เช่นd3ไม่มี API เดียวกันd3ไม่มีSeries/ DataFrameที่มีพฤติกรรมเหมือนในpandas)

คำตอบของอาเหม็ดอธิบายถึงวิธีการ d3 สามารถนำมาใช้ เพื่อให้เกิดการทำงาน DataFrame บางส่วนและบางส่วนของห้องสมุดดังต่อไปนี้ได้รับแรงบันดาลใจจากสิ่งที่ชอบLearnJsDataซึ่งการใช้งานและd3lodash

สำหรับฟีเจอร์ที่เน้น DataFrame ฉันรู้สึกท่วมท้นกับไลบรารี JS ที่ช่วยได้ นี่คือรายการสั้น ๆ ของตัวเลือกบางอย่างที่คุณอาจพบ ฉันยังไม่ได้ตรวจสอบรายละเอียดใด ๆ เลย (ส่วนใหญ่พบในการค้นหา Google + NPM ร่วมกัน)

โปรดใช้ความระมัดระวังในการใช้งานที่หลากหลาย บางตัวเป็น Node.js หรือที่เรียกว่า Javascript ฝั่งเซิร์ฟเวอร์บางตัวเป็น Javascript ฝั่งไคลเอ็นต์ที่เข้ากันได้กับเบราว์เซอร์ บางประเภทเป็นประเภท

  • แพนด้า -Js
    • จากคำตอบของSTEELและFeras
    • "pandas.js เป็นไลบรารีโอเพ่นซอร์ส (ทดลอง) ที่เลียนแบบไลบรารี Python ของแพนด้าโดยอาศัย Immutable.js เป็นค่าเทียบเท่าตรรกะ NumPy อ็อบเจ็กต์ข้อมูลหลักใน pandas.js ก็เหมือนกับใน Python pandas, Series และ DataFrame .”
  • ดาต้าเฟรม -Js
    • "DataFrame-js มีโครงสร้างข้อมูลที่ไม่เปลี่ยนรูปสำหรับจาวาสคริปต์และดาต้าสโคป DataFrame ซึ่งช่วยให้สามารถทำงานกับแถวและคอลัมน์ด้วย sql และ API ที่ได้รับแรงบันดาลใจจากการเขียนโปรแกรมเชิงฟังก์ชัน"
  • ข้อมูลปลอม
    • เห็นในคำตอบของ Ashley Davis
    • "ชุดเครื่องมือการแปลงและวิเคราะห์ข้อมูล JavaScript ที่ได้รับแรงบันดาลใจจาก Pandas และ LINQ"
    • โปรดทราบว่าที่เก็บข้อมูลปลอมแปลง JSจะไม่ได้รับการดูแลอีกต่อไป ตอนนี้ที่เก็บใหม่ใช้ typescript
  • jsdataframe
    • "Jsdataframe คือไลบรารีการโต้เถียงข้อมูล JavaScript ที่ได้รับแรงบันดาลใจจากฟังก์ชัน data frame ใน R และ Python Pandas"
  • ดาต้าเฟรม
    • "สำรวจข้อมูลโดยการจัดกลุ่มและลด"

หลังจากมาถึงคำถามนี้ตรวจสอบคำตอบอื่น ๆ ที่นี่และทำการค้นหาเพิ่มเติมฉันพบตัวเลือกเช่น:

  • Apache Arrow ใน JS
    • ขอบคุณคำแนะนำของผู้ใช้ Back2Basics:
    • "Apache Arrow เป็นข้อกำหนดโครงร่างหน่วยความจำแบบคอลัมน์สำหรับการเข้ารหัสเวกเตอร์และคอนเทนเนอร์แบบตารางของข้อมูลแบบแบนและซ้อนกัน Apache Arrow เป็นมาตรฐานใหม่สำหรับข้อมูลคอลัมน์ในหน่วยความจำขนาดใหญ่ (Spark, Pandas , Drill, Graphistry, ... )"
  • สังเกตได้
    • เมื่อมองแวบแรกดูเหมือนจะเป็นJSทางเลือกหนึ่งสำหรับ"โน้ตบุ๊ก"ของ IPython / Jupyter
    • หน้าของ Observable สัญญาว่า: "การเขียนโปรแกรมเชิงโต้ตอบ", "ชุมชน", บน "แพลตฟอร์มเว็บ"
    • ดูบทนำ 5 นาทีที่นี่
  • เอนกาย (จากคำตอบของรูฟัส )
    • ฉันคาดหวังว่าจะเน้นไปที่ API ของ DataFrame ซึ่ง Pandas เองก็พยายาม รักษาจาก R เอกสารทดแทน / ปรับปรุง / จดหมายไปที่ฟังก์ชั่น
    • แต่ฉันพบว่าตัวอย่างของการปรับเอนเน้นย้ำ วิธี jQuery ในการรับข้อมูลเข้าสู่ DOMMultiview (UI) ที่ยอดเยี่ยมซึ่งไม่ต้องใช้ jQuery แต่ต้องใช้เบราว์เซอร์! ตัวอย่างเพิ่มเติม
    • ... หรือให้ความสำคัญกับมันสถาปัตยกรรม MVC-ish ; รวมถึงเนื้อหาส่วนหลัง (เช่นการเชื่อมต่อฐานข้อมูล)
    • ฉันอาจจะรุนแรงเกินไป ท้ายที่สุดสิ่งที่ดีอย่างหนึ่งเกี่ยวกับแพนด้าคือการสร้างภาพได้อย่างง่ายดาย ออกจากกล่อง.
  • js-data
    • ORMมากขึ้นจริงๆ! ส่วนใหญ่โมดูลตรงตามลักษณะที่แตกต่างกันข้อมูลที่จัดเก็บคำถาม ( js-data-mongodb, js-data-redis, js-data-cloud-datastore) การเรียงลำดับการกรอง ฯลฯ
    • ด้านบวกทำงานบน Node.js เป็นลำดับแรก "ทำงานใน Node.js และในเบราว์เซอร์"
  • มิโซะ (คำแนะนำอื่นจากรูฟัส )
  • AlaSQL
    • "AlaSQL" เป็นฐานข้อมูล SQL แบบโอเพนซอร์สสำหรับ Javascript โดยเน้นที่ความเร็วในการสืบค้นและความยืดหยุ่นของแหล่งข้อมูลสำหรับทั้งข้อมูลเชิงสัมพันธ์และข้อมูลแบบไม่ใช้สคีมา ใช้งานได้ในเบราว์เซอร์ Node.js และ Cordova ของคุณ "
  • การทดลองทางความคิดบางอย่าง:

ฉันหวังว่าโพสต์นี้จะกลายเป็นวิกิชุมชนและประเมิน (เช่นเปรียบเทียบตัวเลือกต่างๆด้านบน) กับเกณฑ์ต่างๆเช่น:

  • เกณฑ์ของแพนด้าในการเปรียบเทียบ R
    • ประสิทธิภาพ
    • ฟังก์ชันการทำงาน / ความยืดหยุ่น
    • สะดวกในการใช้
  • ข้อเสนอแนะของฉันเอง
    • ความคล้ายคลึงกับ Pandas / Dataframe API
    • ความนิยมโดยเฉพาะในคุณสมบัติหลักของพวกเขา
    • เน้นข้อมูลวิทยาศาสตร์> เน้น UI
    • แสดงให้เห็นถึงการผสานรวมกับเครื่องมืออื่น ๆ เช่นJupyter (สมุดบันทึกแบบโต้ตอบ) เป็นต้น

บางสิ่งที่ไลบรารี JS อาจไม่เคยทำ (แต่ทำได้หรือไม่)


1
ขอบคุณสำหรับภาพรวมที่ยอดเยี่ยมนี้ ฉันรู้ทั้งการใช้ดาต้าเฟรมของแพนด้าและ SQL ข้อดี (และข้อเสีย) ของการใช้ JS โดยใช้ dataframes กับฐานข้อมูล JS SQL คืออะไร
ควานหา

@molotow นี่เป็นคำถามที่ดี แต่ฉันไม่มีประสบการณ์กับฐานข้อมูล JS SQL มากนัก (แม้ว่าจะดูดี) โดยทั่วไปฉันเดาว่าวิธีการแบบดาต้าเฟรมจะสนับสนุนฟังก์ชันที่เน้น "data wrangling" / "data-science" มากขึ้นเช่นการอนุมานค่าว่าง การคูณเมทริกซ์ ฯลฯ ในขณะที่ (JS) SQL จะเน้นไปที่เนื้อหาเชิงสัมพันธ์มากกว่าเช่นการสืบค้นการเรียงลำดับการกรอง แน่นอนว่าจะมีการทับซ้อนกัน dataframe สามารถเข้าร่วมเรียงลำดับและกรองได้เช่นเดียวกับ SQL ที่มีฟังก์ชันทางสถิติและอื่น ๆ ใครมีความคิด
The Red Pea

1
ความจริงที่ว่ามีตัวเลือกมากมายจึงน่ารำคาญ แทนที่จะให้ชุมชนมุ่งเน้นไปที่สิ่งเดียวเท่านั้นและทำให้ดี
Claudiu Creanga

3
(ผู้เขียน Arrow JS ที่นี่) @ClaudiuCreanga ฉันเข้าใจความหงุดหงิด ในตอนแรกเราเขียน ArrowJS ด้วยความพยายามที่จะเชื่อมการแบ่งระหว่างโหนด / เบราว์เซอร์และสแต็กข้อมูลขนาดใหญ่แบบดั้งเดิมมากขึ้นและเราได้ลงทุนไปมากที่สุดใน IPC / สตรีมมิงแบบดั้งเดิมที่ยอดเยี่ยมจนถึงขณะนี้ ในขั้นตอนต่อไปเรายินดีที่จะเริ่มผสานรวมกับ JS libs เพิ่มเติม (tensorflow, d3 และอื่น ๆ ) และยินดีต้อนรับ PR เสมอ แนวทางอื่นคือสิ่งต่างๆเช่นโครงการPerspective ของ JPMCซึ่งใช้ ArrowJS เพื่อใช้และสร้างตาราง Arrow
ptaylor

1
มีฟังก์ชันสำหรับการผสานดาต้าเฟรมในแพนด้าเทียบเท่าในจาวาสคริปต์หรือไม่
Phani vikranth

9

ฉันทำงานกับไลบรารีการโต้เถียงข้อมูลสำหรับ JavaScript ที่เรียกว่า data-forge ได้รับแรงบันดาลใจจาก LINQ และ Pandas

สามารถติดตั้งได้ดังนี้:

npm install --save data-forge

ตัวอย่างของคุณจะได้ผลดังนี้:

var csvData = "Source,col1,col2,col3\n" +
    "foo,1,2,3\n" +
    "bar,3,4,5\n";

var dataForge = require('data-forge');
var dataFrame = 
    dataForge.fromCSV(csvData)
        .parseInts([ "col1", "col2", "col3" ])
        ;

หากข้อมูลของคุณอยู่ในไฟล์ CSV คุณสามารถโหลดได้ดังนี้:

var dataFrame = dataForge.readFileSync(fileName)
    .parseCSV()
    .parseInts([ "col1", "col2", "col3" ])
    ;

คุณสามารถใช้selectวิธีการแปลงแถว

คุณสามารถแยกคอลัมน์โดยใช้getSeriesแล้วใช้selectวิธีการแปลงค่าในคอลัมน์นั้น

คุณได้รับข้อมูลของคุณกลับออกจาก data-frame ดังนี้:

var data = dataFrame.toArray();

ในการเฉลี่ยคอลัมน์:

 var avg = dataFrame.getSeries("col1").average();

มีอะไรอีกมากมายที่คุณสามารถทำได้กับสิ่งนี้

คุณสามารถค้นหาเอกสารเพิ่มเติมเกี่ยวกับNPM


8

Ceaveatสิ่งต่อไปนี้ใช้ได้กับ d3 v3 เท่านั้นไม่ใช่ d4v4 ล่าสุด!

ฉันเป็นบางส่วนของd3.jsและแม้ว่าจะไม่สามารถแทนที่ Pandas ได้ทั้งหมด แต่ถ้าคุณใช้เวลาเรียนรู้กระบวนทัศน์ของมัน แต่ก็ควรจะดูแลข้อมูลทั้งหมดของคุณที่กำลังทะเลาะกันให้คุณได้ (และหากคุณต้องการแสดงผลลัพธ์ในเบราว์เซอร์ก็เหมาะอย่างยิ่งสำหรับสิ่งนั้น)

ตัวอย่าง. ไฟล์ CSV ของฉันdata.csv:

name,age,color
Mickey,65,black
Donald,58,white
Pluto,64,orange

ในไดเร็กทอรีเดียวกันให้สร้างindex.htmlสิ่งต่อไปนี้:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8"/>
    <title>My D3 demo</title>

    <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
  </head>
  <body>

      <script charset="utf-8" src="demo.js"></script>
  </body>
</html>

และdemo.jsไฟล์ที่มีสิ่งต่อไปนี้:

d3.csv('/data.csv',

       // How to format each row. Since the CSV file has a header, `row` will be
       // an object with keys derived from the header.
       function(row) {
         return {name : row.name, age : +row.age, color : row.color};
       },

       // Callback to run once all data's loaded and ready.
       function(data) {
         // Log the data to the JavaScript console
         console.log(data);

         // Compute some interesting results
         var averageAge = data.reduce(function(prev, curr) {
           return prev + curr.age;
         }, 0) / data.length;

         // Also, display it
         var ulSelection = d3.select('body').append('ul');
         var valuesSelection =
             ulSelection.selectAll('li').data(data).enter().append('li').text(
                 function(d) { return d.age; });
         var totalSelection =
             ulSelection.append('li').text('Average: ' + averageAge);
       });

ในไดเร็กทอรีให้เรียกใช้python -m SimpleHTTPServer 8181และเปิดhttp: // localhost: 8181ในเบราว์เซอร์ของคุณเพื่อดูรายการอายุและค่าเฉลี่ยอย่างง่าย

ตัวอย่างง่ายๆนี้แสดงคุณสมบัติที่เกี่ยวข้องบางประการของ d3:

  • รองรับการนำเข้าข้อมูลออนไลน์ได้ดีเยี่ยม ( CSV , TSV, JSON ฯลฯ )
  • กึ๋นการโต้เถียงข้อมูลถูกอบเข้ามา
  • การจัดการ DOM ที่ขับเคลื่อนด้วยข้อมูล (อาจเป็นสิ่งที่ยากที่สุดในการคาดเดา): ข้อมูลของคุณจะเปลี่ยนเป็นองค์ประกอบ DOM

2
เพียงเพื่อช่วยมือใหม่ในอนาคต - คำแนะนำข้างต้นใช้ไม่ได้กับ d3 v4 อีกต่อไป คิดว่าขั้นตอนการทำแผนที่เสร็จสิ้นภายในการเรียกกลับข้อมูลตอนนี้เช่นgithub.com/d3/d3-dsv/blob/master/README.md#csvParseRows
swyx

@swyx ขอบคุณที่กรุณาแก้ไขตัวอย่างและโพสต์เป็นคำตอบได้หรือไม่?
Ahmed Fasih

@AhmedFasih คุณควรแก้ไขโพสต์ของคุณเองเพื่อประโยชน์ของทุกคน นอกจากนี้ swyx ยังไม่มีชื่อเสียงเพียงพอที่จะแก้ไขโพสต์ของคุณ
Carles Alcolea

@CarlesAlcolea ฉันได้เพิ่มข้อจำกัดความรับผิดชอบขนาดใหญ่ที่ด้านบนขออภัยฉันไม่มีเวลาเร่งความเร็วของ API ปัจจุบันในขณะนี้😿
Ahmed Fasih

@ AhmedFasih ดีกว่าที่ผ่านมา :) ขอบคุณ!
Carles Alcolea

5

ด้านล่างนี้คือ Python numpy และ pandas

``

import numpy as np
import pandas as pd

data_frame = pd.DataFrame(np.random.randn(5, 4), ['A', 'B', 'C', 'D', 'E'], [1, 2, 3, 4])

data_frame[5] = np.random.randint(1, 50, 5)

print(data_frame.loc[['C', 'D'], [2, 3]])

# axis 1 = Y | 0 = X
data_frame.drop(5, axis=1, inplace=True)

print(data_frame)

``

สิ่งเดียวกันนี้สามารถทำได้ใน JavaScript * [ numjs ใช้ได้เฉพาะกับ Node.js ] แต่ D3.js มีตัวเลือกชุดไฟล์ข้อมูลขั้นสูงมากมาย ทั้ง Numjs และ Pandas-js ยังคงทำงานอยู่ ..

import np from 'numjs';
import { DataFrame } from 'pandas-js';

const df = new DataFrame(np.random.randn(5, 4), ['A', 'B', 'C', 'D', 'E'], [1, 2, 3, 4])

// df
/*

          1         2         3         4
A  0.023126  1.078130 -0.521409 -1.480726
B  0.920194 -0.201019  0.028180  0.558041
C -0.650564 -0.505693 -0.533010  0.441858
D -0.973549  0.095626 -1.302843  1.109872
E -0.989123 -1.382969 -1.682573 -0.637132

*/


5

Pandas.js ในขณะนี้เป็นไลบรารีทดลอง แต่ดูเหมือนว่าจะมีแนวโน้มดีมากที่จะใช้ภายใต้ประทุนที่ไม่เปลี่ยนรูป js และตรรกะ NumpPy ทั้งชุดวัตถุข้อมูลและ DataFrame อยู่ที่นั่น ..


3
ดูเหมือนว่าห้องสมุดจะไม่มีภาระผูกพันมานานกว่าสองปีและดูเหมือนว่าจะมีปัญหามากมาย ฉันจะไม่พูดว่า 'มีแนวโน้มมาก'
jarthur

4

@neversaint การรอคอยของคุณสิ้นสุดลง ยินดีต้อนรับสู่Danfo.jsซึ่งเป็นแพนด้าเช่นไลบรารี Javascript ที่สร้างขึ้นบน tensorflow.js และรองรับเทนเซอร์นอกกรอบ ซึ่งหมายความว่าคุณสามารถแปลงโครงสร้างข้อมูล danfo เป็น Tensors และคุณสามารถทำ groupby, รวม, เข้าร่วม, วางแผนและประมวลผลข้อมูลอื่น ๆ


3

ฉันคิดว่าสิ่งที่ใกล้เคียงที่สุดคือห้องสมุดเช่น:

โดยเฉพาะอย่างยิ่ง Recline มีวัตถุ Dataset ที่มีโครงสร้างค่อนข้างคล้ายกับเฟรมข้อมูลของ Pandas จากนั้นจะช่วยให้คุณสามารถเชื่อมต่อข้อมูลของคุณกับ "มุมมอง" เช่นตารางข้อมูลการสร้างกราฟแผนที่เป็นต้นมุมมองมักจะเป็นแบบบาง ๆ รอบ ๆ ไลบรารีการแสดงภาพพันธุ์ที่ดีที่สุดที่มีอยู่เช่น D3, Flot, SlickGrid เป็นต้น

นี่คือตัวอย่างสำหรับการปรับเอน:

// โหลดข้อมูลบางส่วน
var dataset = recline.Model.Dataset ({
  บันทึก: [
    {value: 1, วันที่: '2012-08-07'},
    {ค่า: 5, b: '2013-09-07'}
  ]
  // โหลดข้อมูล CSV แทน
  // (และ Recline รองรับแหล่งข้อมูลอื่น ๆ อีกมากมาย)
  // url: 'my-local-csv-file.csv',
  // แบ็กเอนด์: 'csv'
});

// รับองค์ประกอบจาก HTML ของคุณสำหรับผู้ดู
var $ el = $ ('# data-viewer');

var allInOneDataViewer = recline.View.MultiView ใหม่ ({
  รุ่น: ชุดข้อมูล
  el: $ el
});
// โปรแกรมดูข้อมูลใหม่ของคุณจะใช้งานได้!

1

การแยกวิเคราะห์ CSV ในจาวาสคริปต์เป็นเรื่องง่ายมากเพราะแต่ละบรรทัดมีอาร์เรย์จาวาสคริปต์อยู่แล้ว หากคุณโหลด csv ของคุณลงในอาร์เรย์ของสตริง (หนึ่งรายการต่อบรรทัด) มันค่อนข้างง่ายที่จะโหลดอาร์เรย์อาร์เรย์ด้วยค่า:

var pivot = function(data){
    var result = [];
    for (var i = 0; i < data.length; i++){
        for (var j=0; j < data[i].length; j++){
            if (i === 0){
                result[j] = [];
            }
            result[j][i] = data[i][j];
        }
    }
    return result;
};

var getData = function() {
    var csvString = $(".myText").val();
    var csvLines = csvString.split(/\n?$/m);

    var dataTable = [];

    for (var i = 0; i < csvLines.length; i++){
        var values;
        eval("values = [" + csvLines[i] + "]");
        dataTable[i] = values;
    }

    return pivot(dataTable);
};

จากนั้นgetData()ส่งคืนอาร์เรย์หลายมิติของค่าตามคอลัมน์

ฉันได้แสดงให้เห็นถึงนี้ในjsFiddleสำหรับคุณ

แน่นอนว่าคุณไม่สามารถทำได้อย่างง่ายดายหากคุณไม่ไว้วางใจอินพุต - หากอาจมีสคริปต์ในข้อมูลของคุณที่ eval อาจรับเป็นต้น


ฉันรู้ว่าคุณเตือนในคำตอบของคุณ แต่ฉันไม่สามารถเครียดได้มากพอว่าวิธีนี้แย่แค่ไหนจากมุมมองด้านความปลอดภัย
xApple

มันแย่มากจากมุมมองด้านความปลอดภัยถ้าเขาไม่เชื่อถือข้อมูลที่ป้อน ตัวอย่างเช่นหากเขากำลังทำโครงการของโรงเรียนซึ่งเขารู้จักไฟล์อินพุตของเขาอยู่แล้ว (เพราะเขาหรือครูของเขาได้จัดเตรียมไว้ล่วงหน้าในรูปแบบเฉพาะ) นี่เป็นวิธีแก้ปัญหาที่กะทัดรัดง่ายและชัดเจน เขาไม่ได้ให้บริบทใด ๆ เกี่ยวกับแหล่งที่มาของอินพุตของเขาเพียงแค่ขอวิธีอ่าน CSV เพื่อการประมวลผลที่ง่าย
Steve K

1

นี่คือวิธีการแบบไดนามิกโดยสมมติว่าส่วนหัวที่มีอยู่ในบรรทัดที่ 1 csv ถูกโหลดด้วยd3.js.

function csvToColumnArrays(csv) {

    var mainObj = {},
    header = Object.keys(csv[0]);

    for (var i = 0; i < header.length; i++) {

        mainObj[header[i]] = [];
    };

    csv.map(function(d) {

        for (key in mainObj) {
            mainObj[key].push(d[key])
        }

    });        

    return mainObj;

}


d3.csv(path, function(csv) {

    var df = csvToColumnArrays(csv);         

});

แล้วคุณจะสามารถเข้าถึงคอลัมน์ของข้อมูลที่คล้ายกัน R, หลามหรือ Matlab dataframe df.column_header[row_number]กับแต่ละ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.