7 استيراد البيانات
7.1 مقدمة
يعد العمل مع البيانات التي توفرها حزم R طريقة رائعة لتعلم أدوات علم البيانات، ولكنك سترغب في مرحلة ما في تطبيق ما تعلمته على بياناتك الخاصة. في هذا الفصل، ستتعلم أساسيات قراءة ملفات البيانات واستيرادها إلى بيئة R.
تحديداً، سينصب تركيز هذا الفصل على قراءة الملفات النصية البسيطة ذات البنية المستطيلة (Plain-text rectangular files). سنبدأ بتقديم نصائح عملية للتعامل مع خصائص البيانات مثل أسماء الأعمدة، وأنواع المتغيرات، والبيانات المفقودة (Missing data). بعد ذلك، ستتعلم كيفية قراءة البيانات من ملفات متعددة في وقت واحد، وكيفية كتابة وتصدير البيانات من R إلى ملف خارجي. أخيراً، ستتعلم كيفية إنشاء وتشكيل أطر البيانات (Data frames) يدوياً داخل R.
7.1.1 المتطلبات المسبقة
في هذا الفصل، ستتعلم كيفية تحميل الملفات المسطحة (Flat files) في R باستخدام حزمة readr، والتي تعد جزءاً أساسياً من المنظومة المركزية لـ tidyverse.
7.2 قراءة البيانات من ملف
للبدء، سنركز على النوع الأكثر شيوعاً لملفات البيانات المستطيلة: ملفات CSV، وهو اختصار لـ (Comma-separated values) أي القيم المفصولة بفاصلة. إليك كيف تبدو بنية ملف CSV بسيط. السطر الأول، والذي يُطلق عليه عادةً سطر العنوان (Header row)، يحدد أسماء الأعمدة، بينما توفر الأسطر الستة التالية البيانات الصغيرة الفعليّة. يتم الفصل بين الأعمدة بواسطة الفواصل.
Student ID,Full Name,favourite.food,mealPlan,AGE
1,Sunil Huffmann,Strawberry yoghurt,Lunch only,4
2,Barclay Lynn,French fries,Lunch only,5
3,Jayendra Lyne,N/A,Breakfast and lunch,7
4,Leon Rossini,Anchovies,Lunch only,
5,Chidiegwu Dunkel,Pizza,Breakfast and lunch,five
6,Güvenç Attila,Ice cream,Lunch only,6
يعرض الجدول 7.1 تمثيلاً لنفس هذه البيانات في شكل جدول منسق.
| Student ID | Full Name | favourite.food | mealPlan | AGE |
|---|---|---|---|---|
| 1 | Sunil Huffmann | Strawberry yoghurt | Lunch only | 4 |
| 2 | Barclay Lynn | French fries | Lunch only | 5 |
| 3 | Jayendra Lyne | N/A | Breakfast and lunch | 7 |
| 4 | Leon Rossini | Anchovies | Lunch only | NA |
| 5 | Chidiegwu Dunkel | Pizza | Breakfast and lunch | five |
| 6 | Güvenç Attila | Ice cream | Lunch only | 6 |
يمكننا قراءة هذا الملف واستيراده إلى R باستخدام الدالة read_csv(). الوسيط الأول هو الأهم على الإطلاق: مسار الملف (Path). يمكنك التفكير في المسار على أنه عنوان الملف؛ فالملف يُسمى students.csv ويقع داخل مجلد data.
students <- read_csv("data/students.csv")
#> Rows: 6 Columns: 5
#> ── Column specification ─────────────────────────────────────────────────────
#> Delimiter: ","
#> chr (4): Full Name, favourite.food, mealPlan, AGE
#> dbl (1): Student ID
#>
#> ℹ Use `spec()` to retrieve the full column specification for this data.
#> ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.سيعمل الكود أعلاه بنجاح إذا كان ملف students.csv موجوداً بالفعل داخل مجلد data في مشروعك الحالي. يمكنك تحميل ملف students.csv من الرابط https://pos.it/r4ds-students-csv أو يمكنك قراءته مباشرة من ذلك الرابط عبر الإنترنت باستخدام:
students <- read_csv("https://pos.it/r4ds-students-csv")عند تشغيل الدالة read_csv()، فإنها تطبع رسالة تخبرك بعدد صفوف وأعمدة البيانات، والمحدد (Delimiter) المستخدم، ومواصفات الأعمدة (أسماء الأعمدة مصنفة حسب نوع البيانات الذي يحتويه كل عمود). كما تطبع أيضاً بعض المعلومات حول كيفية استرداد مواصفات الأعمدة الكاملة وكيفية كتم هذه الرسالة. تعد هذه الرسالة جزءاً لا يتجزأ من آلية عمل readr، وسنعود إليها بالتفصيل في قسم 7.3.
7.2.1 نصائح عملية
بمجرد قراءة البيانات واستيرادها، تتضمن الخطوة الأولى عادةً تحويلها أو تشكيلها بطريقة ما تجعل التعامل معها أكثر سهولة في بقية مراحل التحليل الإحصائي. دعنا نلقي نظرة أخرى على بيانات الطلاب students مع وضع هذا الأمر في الاعتبار.
students
#> # A tibble: 6 × 5
#> `Student ID` `Full Name` favourite.food mealPlan AGE
#> <dbl> <chr> <chr> <chr> <chr>
#> 1 1 Sunil Huffmann Strawberry yoghurt Lunch only 4
#> 2 2 Barclay Lynn French fries Lunch only 5
#> 3 3 Jayendra Lyne N/A Breakfast and lunch 7
#> 4 4 Leon Rossini Anchovies Lunch only <NA>
#> 5 5 Chidiegwu Dunkel Pizza Breakfast and lunch five
#> 6 6 Güvenç Attila Ice cream Lunch only 6في العمود المسمى favourite.food، توجد مجموعة من الأطعمة، ثم تظهر السلسلة النصية N/A، والتي كان ينبغي أن تكون قيم مفقودة حقيقية NA حتى يتعرف عليها R كقيمة “غير متاحة” (not available). هذا أمر يمكننا معالجته وتصحيحه باستخدام الوسيط na. بشكل افتراضي، لا تتعرف الدالة read_csv() في هذه البيانات إلا على السلاسل النصية الفارغة ("") كقيم مفقودة NA، ونحن نريدها أن تتعرف أيضاً على السلسلة النصية "N/A" وتعتبرها قيمة مفقودة.
students <- read_csv("data/students.csv", na = c("N/A", ""))
students
#> # A tibble: 6 × 5
#> `Student ID` `Full Name` favourite.food mealPlan AGE
#> <dbl> <chr> <chr> <chr> <chr>
#> 1 1 Sunil Huffmann Strawberry yoghurt Lunch only 4
#> 2 2 Barclay Lynn French fries Lunch only 5
#> 3 3 Jayendra Lyne <NA> Breakfast and lunch 7
#> 4 4 Leon Rossini Anchovies Lunch only <NA>
#> 5 5 Chidiegwu Dunkel Pizza Breakfast and lunch five
#> 6 6 Güvenç Attila Ice cream Lunch only 6قد تلاحظ أيضاً أن العمودين Student ID و Full Name محاطان بعلامات اقتباس مائلة خلفية (Backticks). السبب في ذلك هو أنهما يحتويان على مسافات فاصلة، مما يكسر قواعد R المعتادة لتسمية المتغيرات؛ وهي ما نسميها بـ الأسماء غير المتوافقة مع قواعد الصياغة (Non-syntactic names). وللإشارة إلى هذه المتغيرات واستدعائها، ستحتاج إلى إحاطتها بعلامات الاقتباس المائلة الخلفية هكذا `:
students |>
rename(
student_id = `Student ID`,
full_name = `Full Name`
)
#> # A tibble: 6 × 5
#> student_id full_name favourite.food mealPlan AGE
#> <dbl> <chr> <chr> <chr> <chr>
#> 1 1 Sunil Huffmann Strawberry yoghurt Lunch only 4
#> 2 2 Barclay Lynn French fries Lunch only 5
#> 3 3 Jayendra Lyne <NA> Breakfast and lunch 7
#> 4 4 Leon Rossini Anchovies Lunch only <NA>
#> 5 5 Chidiegwu Dunkel Pizza Breakfast and lunch five
#> 6 6 Güvenç Attila Ice cream Lunch only 6هناك أسلوب بديل يتمثل في استخدام الدالة janitor::clean_names() والتي تعتمد على خوارزميات تقريبية لتحويل جميع الأسماء إلى نمط snake case دفعة واحدة1.
students |> janitor::clean_names()
#> # A tibble: 6 × 5
#> student_id full_name favourite_food meal_plan age
#> <dbl> <chr> <chr> <chr> <chr>
#> 1 1 Sunil Huffmann Strawberry yoghurt Lunch only 4
#> 2 2 Barclay Lynn French fries Lunch only 5
#> 3 3 Jayendra Lyne <NA> Breakfast and lunch 7
#> 4 4 Leon Rossini Anchovies Lunch only <NA>
#> 5 5 Chidiegwu Dunkel Pizza Breakfast and lunch five
#> 6 6 Güvenç Attila Ice cream Lunch only 6من المهام الشائعة الأخرى بعد استيراد البيانات هي مراجعة أنواع المتغيرات وتدقيقها. على سبيل المثال، يعد المتغير meal_plan متغيراً فئوياً (Categorical variable) يحتوي على مجموعة محددة ومعروفة من القيم الممكنة، وبالتالي يفضل تمثيله في بيئة R كعامل (Factor):
students |>
janitor::clean_names() |>
mutate(meal_plan = factor(meal_plan))
#> # A tibble: 6 × 5
#> student_id full_name favourite_food meal_plan age
#> <dbl> <chr> <chr> <fct> <chr>
#> 1 1 Sunil Huffmann Strawberry yoghurt Lunch only 4
#> 2 2 Barclay Lynn French fries Lunch only 5
#> 3 3 Jayendra Lyne <NA> Breakfast and lunch 7
#> 4 4 Leon Rossini Anchovies Lunch only <NA>
#> 5 5 Chidiegwu Dunkel Pizza Breakfast and lunch five
#> 6 6 Güvenç Attila Ice cream Lunch only 6لاحظ أن القيم الفعلية في المتغير meal_plan ظلت كما هي دون تغيير، ولكن نوع المتغير المكتوب أسفل اسم العمود قد تغير من سلسلة نصية (<chr>) إلى عامل فئوي (<fct>). ستتعلم المزيد عن العوامل الفئوية في الفصل 16.
قبل أن تقوم بتحليل هذه البيانات، من المرجح أنك سترغب في إصلاح العمود age. حالياً، يُعامل العمر كمتغير نصي لأن إحدى المشاهدات قد كُتبت نصياً ككلمة five بدلاً من الرقم 5. سناقش تفاصيل حل هذه المشكلة الإدخالية في الفصل 20.
students <- students |>
janitor::clean_names() |>
mutate(
meal_plan = factor(meal_plan),
age = parse_number(if_else(age == "five", "5", age))
)
students
#> # A tibble: 6 × 5
#> student_id full_name favourite_food meal_plan age
#> <dbl> <chr> <chr> <fct> <dbl>
#> 1 1 Sunil Huffmann Strawberry yoghurt Lunch only 4
#> 2 2 Barclay Lynn French fries Lunch only 5
#> 3 3 Jayendra Lyne <NA> Breakfast and lunch 7
#> 4 4 Leon Rossini Anchovies Lunch only NA
#> 5 5 Chidiegwu Dunkel Pizza Breakfast and lunch 5
#> 6 6 Güvenç Attila Ice cream Lunch only 6الدالة الجديدة هنا هي if_else()، والتي تأخذ ثلاث وسيطات أساسية. الوسيط الأول test يجب أن يكون متجهاً منطقياً (Logical vector). وستحتوي النتيجة على قيمة الوسيط الثاني yes عندما يكون الشرط test هو TRUE، وعلى قيمة الوسيط الثالث no عندما يكون الشرط FALSE. هنا نقوم بالصياغة التالية: إذا كان العمر age يساوي السلسلة النصية "five" فاجعله "5"، وإذا لم يكن كذلك فاتركه كما هو دون تغيير. ستتعلم المزيد عن الدالة if_else() والمتجهات المنطقية في الفصل 12.
7.2.2 وسيطات أخرى (Other arguments)
هناك بضع وسيطات مهمة أخرى نحتاج إلى ذكرها، وسيكون من الأسهل توضيحها إذا استعرضنا أولاً حيلة ذكية ومفيدة: حيث يمكن للدالة read_csv() قراءة السلاسل النصية التي تقوم بإنشائها وتنسيقها بنفسك مباشرة داخل الكود لتشبه ملف CSV حقيقي:
read_csv(
"a,b,c
1,2,3
4,5,6"
)
#> Warning: The `file` argument of `read_csv()` should use `I()` for literal data as of
#> readr 2.2.0.
#>
#> # Bad (for example):
#> read_csv("x,y\n1,2")
#>
#> # Good:
#> read_csv(I("x,y\n1,2"))
#> # A tibble: 2 × 3
#> a b c
#> <dbl> <dbl> <dbl>
#> 1 1 2 3
#> 2 4 5 6في العادة، تستخدم الدالة read_csv() السطر الأول من البيانات كـ أسماء للأعمدة، وهو العرف الأكثر شيوعاً في بناء الملفات. ولكن ليس من الغريب أن تتضمن الملفات بضعة أسطر من البيانات الوصفية (Metadata) في أعلى الملف. يمكنك استخدام الأمر skip = n لتخطي أول n من الأسطر، أو استخدام الأمر comment = "#" لإسقاط وحذف جميع الأسطر التي تبدأ برمز الهاشتاج (على سبيل المثال) #:
في حالات أخرى، قد لا تحتوي البيانات على أسماء للأعمدة في السطر الأول من الأساس. يمكنك استخدام الأمر col_names = FALSE لإخبار الدالة read_csv() بعدم معاملة السطر الأول كعناوين، وبدلاً من ذلك سيتم تسميتها تتابعاً من X1 إلى Xn:
read_csv(
"1,2,3
4,5,6",
col_names = FALSE
)
#> # A tibble: 2 × 3
#> X1 X2 X3
#> <dbl> <dbl> <dbl>
#> 1 1 2 3
#> 2 4 5 6كبديل لذلك، يمكنك تمرير متجه نصي (Character vector) إلى col_names ليتم استخدامه مباشرة كـ أسماء رسمية للأعمدة الحالية:
هذه الوسيطات هي كل ما تحتاج إلى معرفته لقراءة الغالبية العظمى من ملفات CSV التي ستصادفها في ممارستك العملية وتحليلاتك البحثية. (أما بالنسبة للحالات الاستثنائية الأخرى، فستحتاج إلى فحص ملف .csv الخاص بك بعناية وقراءة وثائق المساعدة والدعم المخصصة لدالة read_csv() للاطلاع على وسيطاتها العديدة الأخرى).
7.2.3 أنواع الملفات الأخرى
بمجرد إتقانك لاستخدام الدالة read_csv()، يصبح استخدام دوال حزمة readr الأخرى أمراً غاية في السهولة والمباشرة؛ فالمسألة لا تتعدى معرفة الدالة المناسبة التي يتعين عليك الاستعانة بها:
read_csv2()تقرأ الملفات التي تفصل بين حقولها الفاصلة المنقوطة. تستخدم هذه الملفات العلامة;بدلاً من الفاصلة,للفصل بين الحقول، وهي شائعة الاستخدام في الدول التي تعتمد الفاصلة,كعلامة عشرية.read_tsv()تقرأ الملفات المفصولة بمسافات جدولة (tab-delimited).read_delim()تقرأ الملفات بأي محدد فاصِل (Delimiter)، وتحاول تخمين المحدد تلقائياً إذا لم تقم بتحديده.read_fwf()تقرأ ملفات العرض الثابت (Fixed-width). ويمكنك تحديد الحقول إما عن طريق عرضها باستخدامfwf_widths()أو عن طريق مواضعها باستخدامfwf_positions().read_table()تقرأ صيغة شائعة من ملفات العرض الثابت حيث يتم الفصل بين الأعمدة بواسطة مسافات بيضاء (White space).read_log()تقرأ ملفات سجلات النظام المصممة بنمط (Apache-style log).
7.2.4 تمارين
ما هي الدالة التي ستستخدمها لقراءة ملف يتم الفصل بين حقوله باستخدام الرمز “|”؟
بالإضافة إلى الوسيطات
fileوskipوcomment، ما هي الوسيطات الأخرى المشتركة بين الدالتينread_csv()وread_tsv()؟ما هي الوسيطات الأكثر أهمية بالنسبة للدالة
read_fwf()؟-
في بعض الأحيان، تحتوي السلاسل النصية داخل ملف CSV على فواصل. ولتجنب تسببها في حدوث مشكلات في القراءة، يجب إحاطتها بعلامة اقتباس، مثل
"أو'. بشكل افتراضي، تفترض الدالةread_csv()أن علامة الاقتباس ستكون". لقراءة النص التالي وتحويله إلى إطار بيانات (Data frame)، ما هو الوسيط الذي تحتاج إلى تحديده في الدالةread_csv()؟"x,y\n1,'a,b'" -
حدد الخلل أو الخطأ في كل من ملفات CSV المضمنة التالية. وماذا يحدث عند تشغيل الكود؟
-
تدرب على الإشارة إلى الأسماء غير المتوافقة مع قواعد الصياغة (Non-syntactic names) في إطار البيانات التالي عن طريق:
أ. استخراج المتغير المسمى
1.
ب. رسم مخطط التشتت (Scatterplot) يمثل المتغير1مقابل المتغير2.
ج. إنشاء عمود جديد يسمى3، وهو ناتج قسمة المتغير2على المتغير1.
د. إعادة تسمية الأعمدة إلىoneوtwoوthree.
7.3 التحكم في أنواع الأعمدة
لا يحتوي ملف CSV على أي معلومات حول نوع كل متغير (أي ما إذا كان منطقياً، أو رقمياً، أو نصياً، إلخ)، ولذلك ستحاول حزمة readr تخمين النوع تلقائياً. يصف هذا القسم كيفية عمل عملية التخمين، وكيفية حل بعض المشكلات الشائعة التي تؤدي إلى فشلها، وكيفية تحديد أنواع الأعمدة بنفسك إذا دعت الحاجة. أخيراً، سنذكر بضع استراتيجيات عامة ومفيدة إذا فشلت readr فشلاً ذريعاً وكنت بحاجة إلى الحصول على نظرة أعمق لبنية ملفك وتكوينه.
7.3.1 تخمين الأنواع
تستخدم readr آلية تقريبية (Heuristic) لتحديد أنواع الأعمدة. لكل عمود، تقوم الحزمة بسحب قيم 1000 صف2 متباعدة بشكل متساوٍ من الصف الأول إلى الصف الأخير، مع تجاهل القيم المفقودة. ثم تبدأ في فحص الأسئلة التالية تتابعاً:
- هل يحتوي العمود فقط على
FأوTأوFALSEأوTRUE(مع تجاهل حالة الأحرف)؟ إذا كان الأمر كذلك، فهو متغير منطقي (Logical). - هل يحتوي فقط على أرقام (مثل
1أو-4.5أو5e6أوInf)؟ إذا كان الأمر كذلك، فهو متغير عددي (Number). - هل يتطابق مع معيار ISO8601 الدولي؟ إذا كان الأمر كذلك، فهو تاريخ (Date) أو تاريخ ووقت (Date-time). (سنعود إلى تفاصيل التواريخ والأوقات بمزيد من التفصيل في قسم 17.2).
- خلاف ذلك، لا بد أن يكون المتغير سلسلة نصية (String).
يمكنك رؤية هذا السلوك عملياً في هذا المثال البسيط:
read_csv("
logical,numeric,date,string
TRUE,1,2021-01-15,abc
false,4.5,2021-02-15,def
T,Inf,2021-02-16,ghi
")
#> # A tibble: 3 × 4
#> logical numeric date string
#> <lgl> <dbl> <date> <chr>
#> 1 TRUE 1 2021-01-15 abc
#> 2 FALSE 4.5 2021-02-15 def
#> 3 TRUE Inf 2021-02-16 ghiتعمل هذه الآلية التقريبية بشكل ممتاز إذا كان لديك مجموعة بيانات نظيفة، ولكن في الحياة الواقعية والعملية، ستواجه باقة متنوعة من حالات الفشل الغريبة والمثيرة.
7.3.2 القيم المفقودة، أنواع الأعمدة، والمشكلات
الطريقة الأكثر شيوعاً لفشل الكشف التلقائي عن نوع العمود هي احتواء العمود على قيم غير متوقعة، مما يترتب عليه ظهور عمود نصي (character) بدلاً من نوع أكثر تحديداً وملائمة لطبيعة المتغير. وأحد أكثر الأسباب شيوعاً لحدوث ذلك هو وجود قيمة مفقودة تم تسجيلها باستخدام رمز آخر غير الرمز NA الذي تتوقعه حزمة readr عادةً.
خذ ملف CSV البسيط المكون من عمود واحد كمثال:
simple_csv <- "
x
10
.
20
30"إذا قمنا بقراءته دون أي وسيطات إضافية، يتحول x إلى عمود نصي:
read_csv(simple_csv)
#> # A tibble: 4 × 1
#> x
#> <chr>
#> 1 10
#> 2 .
#> 3 20
#> 4 30في هذه الحالة الصغيرة جداً، يمكنك بسهولة رؤية القيمة المفقودة الممثلة بالنقطة .. ولكن ماذا يحدث إذا كان لديك آلاف الصفوف مع وجود بضع قيم مفقودة فقط ممثلة بنقاط . منثورة بينها؟ أحد الأساليب للتعامل مع هذا هو إخبار readr بأن x هو عمود عددي، ثم تتبع الموضع الذي يفشل فيه الكود لمعرفة السبب. يمكنك القيام بذلك باستخدام الوسيط col_types، والذي يأخذ قائمة مسماة (Named list) تتطابق فيها الأسماء مع أسماء الأعمدة في ملف CSV:
df <- read_csv(
simple_csv,
col_types = list(x = col_double())
)
#> Warning: One or more parsing issues, call `problems()` on your data frame for
#> details, e.g.:
#> dat <- vroom(...)
#> problems(dat)الآن تفيد الدالة read_csv() بوجود مشكلة (Problem)، وتخبرنا أنه يمكننا معرفة المزيد من التفاصيل باستخدام الدالة problems():
problems(df)
#> # A tibble: 1 × 5
#> row col expected actual file
#> <int> <int> <chr> <chr> <chr>
#> 1 3 1 a double . /tmp/RtmpsFZbxE/vroom-chr-to-file-20932fb2db15يوضح لنا هذا التقرير وجود مشكلة في الصف الثالث، العمود الأول، حيث كانت readr تتوقع رقماً عشرياً (Double) ولكنها وجدت نقطة .. يشير هذا بوضوح إلى أن مجموعة البيانات هذه تستخدم الرمز . للتعبير عن القيم المفقودة. بناءً على ذلك، نقوم بتحديد na = "."، وعندها ينجح التخمين التلقائي تماماً، مما يمنحنا العمود العددي الذي نرغب فيه بالأساس:
read_csv(simple_csv, na = ".")
#> # A tibble: 4 × 1
#> x
#> <dbl>
#> 1 10
#> 2 NA
#> 3 20
#> 4 307.3.3 أنواع الأعمدة
توفر حزمة readr ما مجموعه تسعة أنواع من الأعمدة لتختار من بينها واستخدامها:
-
col_logical()وcol_double()لقراءة القيم المنطقية والأعداد الحقيقية. ونادراً ما تحتاج إليها (إلا في الحالات المذكورة أعلاه)، نظرًا لأن readr ستخمنها بالنيابة عنك في العادة. -
col_integer()لقراءة الأعداد الصحيحة. ونحن نادراً ما نفرق بين الأعداد الصحيحة (Integers) والأعداد العشرية الحقيقية (Doubles) في هذا الكتاب لأنها متكافئة وظيفياً، ولكن قراءة الأعداد الصحيحة صراحةً قد تكون مفيدة أحياناً لأنها تشغل نصف المساحة التي تستهلكها الأعداد العشرية في الذاكرة العشوائية. -
col_character()لقراءة السلاسل النصية. وقد يكون من المفيد تحديد ذلك صراحةً عندما يكون لديك عمود يمثل معرفاً رقمياً (Numeric identifier)، أي سلسلة طويلة من الأرقام التي تحدد هوية كائن ما ولكن لا معنى لتطبيق العمليات الحسابية عليها؛ ومن الأمثلة على ذلك: أرقام الهواتف، وأرقام البطاقات الشخصية، وأرقام بطاقات الائتمان، إلخ. -
col_factor()وcol_date()وcol_datetime()لإنشاء العوامل الفئوية، والتواريخ، والتواريخ الملحقة بالوقت على التوالي؛ وستتعلم المزيد عنها عندما نصل إلى فصول أنواع البيانات تلك في الفصل 16 و الفصل 17. -
col_number()هو محلل عددي مرن يتجاهل المكونات غير الرقمية، وهو مفيد بشكل خاص لقراءة العملات (Currencies). وستتعلم المزيد عنه في الفصل 13. -
col_skip()يتخطى عموداً معيناً فلا يتم تضمينه في النتيجة، وهو أمر مفيد لتسريع قراءة البيانات إذا كان لديك ملف CSV ضخم جداً وتريد استخدام بعض الأعمدة فقط دون غيرها.
من الممكن أيضاً تجاوز الآلية التقريبية الافتراضية التي تخمن الأنواع، وذلك بالانتقال من استخدام الدالة list() إلى الدالة cols() وتحديد الخيار الافتراضي للمتغيرات عبر .default:
another_csv <- "
x,y,z
1,2,3"
read_csv(
another_csv,
col_types = cols(.default = col_character())
)
#> # A tibble: 1 × 3
#> x y z
#> <chr> <chr> <chr>
#> 1 1 2 3هناك دالة مساعدة أخرى مفيدة للغاية وهي cols_only() والتي تقرأ فقط الأعمدة التي تحددها لها صراحة وتتجاهل الباقي:
read_csv(
another_csv,
col_types = cols_only(x = col_character())
)
#> # A tibble: 1 × 1
#> x
#> <chr>
#> 1 17.4 قراءة البيانات من ملفات متعددة
في بعض الأحيان تكون بياناتك مجزأة وموزعة عبر ملفات متعددة بدلاً من إدراجها في ملف واحد. على سبيل المثال، قد تكون لديك بيانات مبيعات لعدة أشهر، بحيث تكون بيانات كل شهر في ملف منفصل: 01-sales.csv لشهر يناير، و 02-sales.csv لشهر فبراير، و 03-sales.csv لشهر مارس. باستخدام الدالة read_csv()، يمكنك قراءة هذه البيانات دفعة واحدة ورصها (دمجها عمودياً) فوق بعضها البعض في إطار بيانات واحد.
sales_files <- c("data/01-sales.csv", "data/02-sales.csv", "data/03-sales.csv")
read_csv(sales_files, id = "file")
#> # A tibble: 19 × 6
#> file month year brand item n
#> <chr> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 data/01-sales.csv January 2019 1 1234 3
#> 2 data/01-sales.csv January 2019 1 8721 9
#> 3 data/01-sales.csv January 2019 1 1822 2
#> 4 data/01-sales.csv January 2019 2 3333 1
#> 5 data/01-sales.csv January 2019 2 2156 9
#> 6 data/01-sales.csv January 2019 2 3987 6
#> # ℹ 13 more rowsمرة أخرى، سيعمل الكود أعلاه بنجاح إذا كانت ملفات CSV هذه موجودة داخل مجلد data في مشروعك الحالي. يمكنك تحميل هذه الملفات من الروابط https://pos.it/r4ds-01-sales و https://pos.it/r4ds-02-sales و https://pos.it/r4ds-03-sales أو يمكنك قراءتها مباشرة من الويب عبر الإنترنت باستخدام الكود التالي:
يضيف الوسيط id عموداً جديداً يسمى file إلى إطار البيانات الناتج، وهو يحدد الملف الذي جاءت منه تلك البيانات. يعد هذا مفيداً بشكل خاص في الحالات التي لا تحتوي فيها الملفات المستوردة على عمود تعريفي يمكن أن يساعدك في تتبع المشاهدات والرجوع بها إلى مصادرها الأصلية.
وإذا كان لديك ملفات كثيرة جداً تريد قراءتها، فقد يصبح من المرهق والمزعج كتابة أسمائها كلها في قائمة. بدلاً من ذلك, يمكنك استخدام الدالة الأساسية list.files() للبحث عن الملفات تلقائياً عن طريق مطابقة نمط معين (Pattern) في أسماء الملفات. وستتعلم المزيد عن هذه الأنماط والتعبيرات النمطية في الفصل 15.
sales_files <- list.files("data", pattern = "sales\\.csv$", full.names = TRUE)
sales_files
#> [1] "data/01-sales.csv" "data/02-sales.csv" "data/03-sales.csv"7.5 الكتابة والتصدير إلى ملف
تأتي حزمة readr أيضاً مع دالتين مفيدتين للغاية لكتابة البيانات وحفظها على القرص الصلب: write_csv() و write_tsv(). الوسيطات الأكثر أهمية لهذه الدوال هي x (إطار البيانات المراد حفظه) و file (الموقع والمسار المخصص لحفظه). ويمكنك أيضاً تحديد كيفية كتابة وحفظ القيم المفقودة باستخدام الوسيط na، وما إذا كنت تريد إلحاق البيانات بملف موجود بالفعل عبر الوسيط append.
write_csv(students, "students.csv")لنحاول الآن قراءة ملف CSV هذا مجدداً إلى بيئة البرنامج. لاحظ أن معلومات نوع المتغيرات التي قمت بتهيئتها وتعديلها للتو قد فُقدت تماماً عند الحفظ بصيغة CSV، لأنك تبدأ من جديد بقراءة الملف كملف نصي بسيط:
students
#> # A tibble: 6 × 5
#> student_id full_name favourite_food meal_plan age
#> <dbl> <chr> <chr> <fct> <dbl>
#> 1 1 Sunil Huffmann Strawberry yoghurt Lunch only 4
#> 2 2 Barclay Lynn French fries Lunch only 5
#> 3 3 Jayendra Lyne <NA> Breakfast and lunch 7
#> 4 4 Leon Rossini Anchovies Lunch only NA
#> 5 5 Chidiegwu Dunkel Pizza Breakfast and lunch 5
#> 6 6 Güvenç Attila Ice cream Lunch only 6
write_csv(students, "students-2.csv")
read_csv("students-2.csv")
#> # A tibble: 6 × 5
#> student_id full_name favourite_food meal_plan age
#> <dbl> <chr> <chr> <chr> <dbl>
#> 1 1 Sunil Huffmann Strawberry yoghurt Lunch only 4
#> 2 2 Barclay Lynn French fries Lunch only 5
#> 3 3 Jayendra Lyne <NA> Breakfast and lunch 7
#> 4 4 Leon Rossini Anchovies Lunch only NA
#> 5 5 Chidiegwu Dunkel Pizza Breakfast and lunch 5
#> 6 6 Güvenç Attila Ice cream Lunch only 6هذا يجعل ملفات CSV غير موثوقة تماماً لتخزين وحفظ النتائج المؤقتة—حيث ستحتاج إلى إعادة إنشاء مواصفات الأعمدة وتعديل الأنواع في كل مرة تقوم فيها بتحميل البيانات. وهناك بديلان رئيسيان لتجاوز هذه المشكلة:
-
الدالتان
write_rds()وread_rds()هما غلافان موحدان (Uniform wrappers) مبنيان حول الدوال الأساسيةsaveRDS()وreadRDS(). وتقوم هذه الدوال بتخزين البيانات بالصيغة الثنائية الخاصة بلغة R والتي تُسمى RDS. هذا يعني أنه عندما تعيد تحميل الكائن، فإنك تقوم بتحميل نفس كائن R تماماً وبنفس خصائصه وأنواعه التي قمت بحفظها وتخزينها.write_rds(students, "students.rds") read_rds("students.rds") #> # A tibble: 6 × 5 #> student_id full_name favourite_food meal_plan age #> <dbl> <chr> <chr> <fct> <dbl> #> 1 1 Sunil Huffmann Strawberry yoghurt Lunch only 4 #> 2 2 Barclay Lynn French fries Lunch only 5 #> 3 3 Jayendra Lyne <NA> Breakfast and lunch 7 #> 4 4 Leon Rossini Anchovies Lunch only NA #> 5 5 Chidiegwu Dunkel Pizza Breakfast and lunch 5 #> 6 6 Güvenç Attila Ice cream Lunch only 6 -
تتيح لك حزمة arrow قراءة وكتابة ملفات parquet، وهي صيغة ملفات ثنائية سريعة جداً يمكن مشاركتها واستخدامها عبر لغات برمجة مختلفة (مثل بايثون). وسنعود إلى حزمة arrow بمزيد من التفصيل والعمق في الفصل 22.
library(arrow) write_parquet(students, "students.parquet") read_parquet("students.parquet") #> # A tibble: 6 × 5 #> student_id full_name favourite_food meal_plan age #> <dbl> <chr> <chr> <fct> <dbl> #> 1 1 Sunil Huffmann Strawberry yoghurt Lunch only 4 #> 2 2 Barclay Lynn French fries Lunch only 5 #> 3 3 Jayendra Lyne NA Breakfast and lunch 7 #> 4 4 Leon Rossini Anchovies Lunch only NA #> 5 5 Chidiegwu Dunkel Pizza Breakfast and lunch 5 #> 6 6 Güvenç Attila Ice cream Lunch only 6
تتميز ملفات Parquet بأنها أسرع بكثير من ملفات RDS ويمكن استخدامها خارج بيئة R، ولكنها تتطلب تثبيت واستدعاء حزمة arrow.
7.6 إدخال البيانات يدوياً (Data entry)
في بعض الأحيان، ستحتاج إلى بناء وتجميع جدول بيانات (tibble) “يدوياً” عن طريق إدخال كمية صغيرة من البيانات مباشرة داخل سكربت R الخاص بك. هناك دالتان مفيدتان لمساعدتك في القيام بذلك، وتختلفان في طريقة ترتيب وتنسيق الجدول؛ فإما أن ترتبه بناءً على الأعمدة أو بناءً على الصفوف. تعمل الدالة tibble() عن طريق ترتيب البيانات في شكل أعمدة (By column):
إن ترتيب وتنسيق البيانات بواسطة الأعمدة قد يجعل من الصعب رؤية كيفية ارتباط الصفوف وتوافقها مع بعضها البعض، ولذلك فإن البديل الأفضل هو استخدام الدالة tribble()، وهي اختصار لـ (transposed tibble) أي الجدول المنقول، والتي تتيح لك ترتيب وتنسيق بياناتك صفاً بصف (Row by row). تم تصميم الدالة tribble() خصيصاً لتسهيل إدخال البيانات يدوياً داخل الكود: حيث تبدأ عناوين الأعمدة بعلامة المدة ~ ويتم الفصل بين المدخلات أو البيانات باستخدام الفواصل commas. هذا يجعل من الممكن صياغة وعرض كميات صغيرة من البيانات في شكل منسق للغاية وسهل القراءة البصرية:
tribble(
~x, ~y, ~z,
1, "h", 0.08,
2, "m", 0.83,
5, "g", 0.60
)
#> # A tibble: 3 × 3
#> x y z
#> <dbl> <chr> <dbl>
#> 1 1 h 0.08
#> 2 2 m 0.83
#> 3 5 g 0.67.7 ملخص
في هذا الفصل، تعلمت كيفية تحميل ملفات CSV واستيرادها باستخدام الدالة read_csv()، وكيفية إدخال بياناتك الخاصة يدوياً باستخدام الدالتين tibble() و tribble(). وقد تعلمت أيضاً كيفية عمل ملفات csv، وبعض المشكلات الشائعة التي قد تواجهك، وكيفية التغلب عليها وتصحيحها. سوف نعود إلى موضوع استيراد البيانات عدة مرات في هذا الكتاب: حيث سيعلمك الفصل 20 كيفية استيراد البيانات من ملفات Excel وجداول بيانات Google Sheets، وسيوضح لك الفصل 21 كيفية تحميل البيانات واستدعائها من قواعد البيانات مباشرة، و الفصل 22 من ملفات parquet، و الفصل 23 من ملفات JSON، و الفصل 24 من مواقع الويب المختلفة.
لقد أوشكنا على الوصول إلى نهاية هذا الجزء من الكتاب، ولكن هناك موضوع أخير مهم يتعين علينا تغطيته: وهو كيفية الحصول على المساعدة وتلقي الدعم البرمجي. لذلك، في الفصل القادم، ستتعلم بعض الأماكن والمصادر الجيدة للبحث عن حلول للمشكلات البرمجية، وكيفية إنشاء كود برمي نموذجي وقابل لإعادة الإنتاج (reprex) لزيادة فرصك في الحصول على إجابات ومساعدات ممتازة من المجتمعات البرمجية، بالإضافة إلى بعض النصائح العامة لمواكبة التطورات المستمرة في عالم لغة R الشاسع.