اذهب إلى المحتوى

تطويع البيانات في جافاسكربت


Ali Alrohia

تُعَد البيانات وجافاسكربت للواجهة الأمامية من الناحية العملية أحداثًا متنافية، إذ يرى سوق العمل أن وظيفة الواجهة الخلفية معالجة البيانات وتجميعها، أما وظيفة جافاسكربت فهي عرض البيانات المُجمّعة مسبقًا، كما يُنظر إلى عرض النطاق الترددي وزمن المعالجة على أنهما عقبتان رئيسيتان لدى التعامل مع البيانات في الواجهة الأمامية وهذا صحيح إلى حدٍ كبيرٍ، ولكن توجد حالاتٌ من المجدي فيها معالجة البيانات ضمن المتصفح. إذًا فكيف يمكن النجاح بمعالجة البيانات في هذه الحالات؟

التفكير في البيانات

يتطلب العمل على البيانات باستخدام جافاسكربت امتلاكًا كاملًا للبيانات وفهمًا للأدوات المتوفرة دون اتصالٍ غير ضروريٍ بالخادم، حيث من المفيد التمييز بين البيانات التابعة لثلاثة أطرافٍ والبيانات المُلّخصة.

تتألف البيانات ثلاثية الأطراف من بيانات معاملاتٍ أولية، وهي بيانات منخفضة المستوى يصعب تحليلها لوحدها؛ وتوجد من ناحيةٍ أخرى البيانات المُلخصة وهي البيانات الممكن عرضها بطريقةٍ مفيدةٍ ومنطقية، وتُدعى أيضًا بالبيانات المكوّنة. يهتم المطورون بهيكلية البيانات الكامنة بين تفاصيل المعاملات والبيانات المكوّنة، وهذه هي النقطة التي سنُركز عليها، فهذه البيانات مُجمّعة ولكنها تتضمن أكثر مما نحتاج للعرض النهائي، كما تمتلك بُعدين أو أكثر (متعددة الأبعاد)، وهذا يوفّر مرونةً في طريقة عرضها، فهي تسمح للمستخدمين النهائيين بتشكيل البيانات واستخراج المعلومات لمزيدٍ من التحليل، وبالرغم من أنها صغيرة وذات أداء جيدٍ، إلا أنها توفّر تفاصيلًا تسمح لنا وللمؤلف بالاطلاع على معلوماتٍ لم نكن نتوقعها.

يجب أن لا يكون الهدف هو الوصول بالبيانات إلى هيئةٍ مثالية، وذلك لتجنب أي تلاعبٍ بها في الواجهة الأمامية، بل يجب تقليص البيانات إلى مجموعة بياناتٍ متعددة الأبعاد، كما يجب استخدام أبعادٍ متعددة يهتم بها العميل المُستهدف مثل الأشخاص والمنتجات والأماكن والوقت، وقياساتٍ متعددة مثل المجموع والتعداد والمتوسط وأصغر قيمة وأعظم قيمة، وبالتالي فعلينا في النهاية عرض هذه البيانات على صفحةٍ مع عناصر نموذج تسمح بتقسيم البيانات لتحليلها بصورةٍ أعمق.

يُعَد إنشاء مجموعات البيانات عمليةً دقيقة جدًا، حيث لا بد من امتلاك بياناتٍ كافية ليكون تحليلنا ذو قيمة وبدون إجهاد جهاز العميل سواءً كان حاسوبًا أو هاتفًا ذكيًا أو غير ذلك، وهذا يعني الوصول لمتطلباتٍ واضحة وموجزة، وسوف نحتاج لاستخدام أبعاد متنوعة وقياسات مختلفة، وهذا يعتمد على مدى اتساع مجموعة البيانات التي نتعامل معها.

سنذكر فيما يلي بعض الأمور الواجب أخذها بالحسبان:

  • هل غالبية المحتوى هو محتوى مميز أو سوف يُستخدم مرارًا؟ حيث نعتمد على قاعدة 80/20 التي تنص على أن 80% من المستخدمين عامةً يحتاجون إلى 20% مما هو متوفرٌ.
  • هل كل الأبعاد منتهية؟ حيث يجب أن تمتلك الأبعاد مجموعةً من القيم المُحددة مسبقًا، فمثلًا مخزون منتج ما يزداد باستمرار سوف يتضخم بسرعة، لذلك فإن العمل على تصنيفات المنتج ربما سيكون أفضل.
  • العمل على تجميع البيانات ما أمكن وبالأخص التواريخ، وإن كنت تستطيع الاكتفاء بعملية التجميع باستخدام الأعوام فافعل ذلك، وإن كنت بحاجة للتجميع بالاعتماد على الشهر فلا بأس بذلك، ولكن تجنب المجالات الأصغر من ذلك.
  • اعتماد مبدأ "القلة أفضل"، فكلما كان عدد القيم ضمن البعد أقل، كان هذا أفضل من ناحية الأداء. لنأخذ مجموعة بيانات مؤلفةً من 200 صفٍ على سبيل المثال، فإذا أضفنا بعدًا آخر بأربع قيمٍ ممكنة، فسوف تتضخم مجموعة البيانات هذه إلى 200x4 = 800 صفٍ كحدٍ أعظمي؛ أما إذا أضفت بعدًا بخمسين قيمة، فعندها سوف تتضخم مجموعة البيانات إلى 200x50 = 10000 صفًا، وهذا سوف يتضاعف مع كل بعد نُضيفه.
  • تجنُب القياسات المُلخصة في مجموعات البيانات متعددة الأبعاد لأنها تحتاج إعادة الحساب في كل مرةٍ تتغير فيها مجموعة البيانات، إذ يجب عليك مثلًا تضمين المجموع الكلي والعدد الكلي وحساب المتوسطات ديناميكيًا إذا كنت تخطط لعرض المتوسط، ونتمكن بهذه الطريقة من إعادة حساب المتوسطات باستخدام القيم المُلخصة عند تلخيص البيانات.

تأكد من فهمك الجيد للبيانات التي تعمل عليها قبل البدء بتنفيذ أيٍ من المذكور سابقًا، فمن المحتمل أن تفترض افتراضاتٍ خاطئةٍ تقود إلى قراراتٍ مبنيةٍ على معلوماتٍ خاطئة، لذلك فإن جودة البيانات هي أولويةٌ هامة وهذا ينطبق على البيانات التي تطلبها أو تُنشئها.

لا تأخذ أي مجموعة بيانات وتبني افتراضاتٍ عن بعد أو قياسٍ فيها، ولا تتردد بالسؤال دائمًا عن فهرس البيانات أو أي توثيقٍ خاصٍ بها يُمكن أن يساعد على فهم ما تتعامل معه، حيث لا يعتمد تحليل البيانات على الحدس أو التنبؤ، فربما طُبقت قواعد خاصة بعملٍ تجاريٍ أو حُذفت بيانات في خطوةٍ سابقة، وبالتالي فإن كنت لا تملك هذه المعلومات فسينتهي بك الأمر بإنتاج مجموعات بياناتٍ وتصوراتٍ لا معنى لها أو مضللة بأسوء الأحوال. سوف يساعد المثال التالي على فهم ما سبق.

حالة الاستخدام المدروسة

سوف نستخدم مجموعة بيانات BuzzFeed المختصة بتحليل البيانات المتعلقة بالأماكن التي يأتي منها اللاجئون إلى الولايات المتحدة والأماكن التي يذهبون إليها، حيث سنبني تطبيقًا صغيرًا يعرض لنا عدد اللاجئين الواصلين لولايةٍ محددة في عامٍ محدد، وسنعرض على وجه الخصوص واحدةً مما يلي بناءً على طلب المستخدم:

  • العدد الكلي للواصلين إلى ولايةٍ معينة في عامٍ معينٍ.
  • العدد الكلي للواصلين خلال كل الأعوام لولايةٍ معينة.
  • العدد الكلي للواصلين لكل الولايات في عام معيّن.

سوف تكون واجهة المستخدم عبارة عن نموذجٍ بسيطٍ لاختيار الولاية والعام، وسوف تعمل الشيفرة على:

  1. إرسال طلبٍ بالبيانات.
  2. تحويل النتيجة إلى JSON.
  3. معالجة البيانات.
  4. تسجيل أي خطأ في الطرفية.
  5. عرض النتائج للمستخدم.
اقتباس

ملاحظة: لضمان عدم تنفيذ الخطوة 3 إلا بعد جلب كامل مجموعة البيانات، سوف نستخدم الطريقة then مع تطبيق كل عمليات معالجة البيانات ضمن هذه الكتلة.

لن نمرّر مجموعات بياناتٍ ضخمة الحجم للمتصفح لسببين هما عرض الحزمة، وقدرات وحدة المعالجة المركزية CPU، لذلك ستُجمع البيانات في الخادم باستخدام Node.js.

  • بيانات المصدر
[{"year":2005,"origin":"Afghanistan","dest_state":"Alabama","dest_city":"Mobile","arrivals":0},
{"year":2006,"origin":"Afghanistan","dest_state":"Alabama","dest_city":"Mobile","arrivals":0},
... ]
  • البيانات متعددة الأبعاد:
[{"year": 2005, "state": "Alabama","total": 1386}, 
 {"year": 2005, "state": "Alaska", "total": 989}, 
... ]

002_javascript_taming_data_1.png

كيفية ضبط هيكلية البيانات في المكان الصحيح

واجهة AJAX وFetch API

توجد عدة طرق في جافاسكربت من أجل جلب البيانات من مصدرٍ خارجيٍ، وكان علينا قديمًا استخدام طلب XHR لذلك، لأنه مدعوم على نطاقٍ واسعٍ ولكنه معقد ويتطلب استخدام عدة طرقٍ مختلفة، كما توجد مكتبات تُساعد على تخفيض التعقيد، مثل Axios أو jQuery's Ajax API، كما أنها توفّر دعمًا عبر المتصفحات، ولهذا فهي خيارٌ متاحٌ إن كنت تستخدم أحدها، لكن لا بد من اختيار الحلول الأصيلة ما أمكن. توجد أيضًا طريقة Fetch API وهي الأحدث، ولذلك فهي مدعومةٌ على نطاقٍ أضيق من سابقاتها، ولكنها أسهل وقابلة للتسلسل، وستُحوّل الشيفرة البرمجية إلى مكافئٍ مدعومٍ على نطاقٍ أوسع إن كنت تستخدم ناقلًا مثل Babel.

ستُستخدم واجهة Fetch API في الحالة المدروسة لجلب البيانات للتطبيق.

window.fetchData = window.fetchData || {};
  fetch('./data/aggregate.json')
  .then(response => {
      // when the fetch executes we will convert the response
      // to json format and pass it to .then()
      return response.json();
  }).then(jsonData => {
      // take the resulting dataset and assign to a global object
      window.fetchData.jsonData = jsonData;
  }).catch(err => {
      console.log("Fetch process failed", err);
  });

الشيفرة البرمجية السابقة هي جزءٌ من ملف main.js الموجود في مستودع GitHub، حيث ترسل الطريقة fetch()‎ طلبًا بالبيانات ثم نحوّل النتائج إلى JSON، وستُستخدم الطريقة then()‎ من أجل ضمان عدم تنفيذ التعليمة التالية إلا بعد جلب كامل مجموعة البيانات، وستُنفذُ جميع عمليات معالجة البيانات ضمن هذه الكتلة، كما ستُسجل الأخطاء باستخدام console.log()‎.

الهدف هنا هو تحديد الأبعاد الأساسية المطلوبة لعمل تقريرٍ بالعام والولاية قبل تجميع عدد الواصلين المرتبطين بهذه الأبعاد، وإزالة الدولة الأم والمدينة المتوجهين لها، ويمكنك الاطلاع على النص البرمجي Node.js في الملف preprocess/index.js/ في مستودع GitHub لفهم كيفية إنجاز العمليات السابقة بصورةٍ أكبر، حيث يُنشئ هذا النص البرمجي ملف aggregate.json الذي يجلبه التابع fetch()‎.

البيانات متعددة الأبعاد

الهدف من الصيغة متعددة الأبعاد هو تحقيق المرونة بحيث تكون البيانات مُفصلةً جيدًا، حتى لا يُضطر المستخدم لطلب استعلام للخادم في كل مرةٍ يحتاج فيها إجابةً عن سؤالٍ مختلف، وتكون مُلخصةً بنفس الوقت بحيث لا يُضطر التطبيق للبحث في كامل مجموعة البيانات مع كل جزءٍ جديدٍ من البيانات. يجب علينا توقع الأسئلة وتقديم بيانات تُجيب عن هذه الأسئلة لأن المستخدم يحتاج إلى أن يكون قادرًا على إجراء بعض التحليلات دون أن يشعر بأنه مُقيدٌ أو مغمورٌ بكمٍ هائلٍ من المعلومات.

ستُستخدم بيانات JSON كما هو الحال مع معظم واجهات برمجة التطبيقات APIs، فهي معيارٌ مُستخدمٌ لإرسال البيانات للتطبيقات على أنها كائناتٌ تتألف من أزواج (اسم وقيمة). ألقِ نظرةً على العينة التالية من مجموعة بياناتٍ متعددة الأبعاد قبل العودة إلى الحالة المدروسة:

const ds = [{
  "year": 2005,
  "state": "Alabama",
  "total": 1386,
  "priorYear": 1201
}, {
  "year": 2005,
  "state": "Alaska",
  "total": 811,
  "priorYear": 1541
}, {
  "year": 2006,
  "state": "Alabama",
  "total": 989,
  "priorYear": 1386
}];

تستطيع استخدام جافاسكربت على نحوٍ أكبر لتحليل مجموعة البيانات عندما تكون مُجمّعةً بطريقة صحيحة. لنُلقِ نظرةً على بعض من توابع مصفوفات جافاسكربت المُستخدمة لتكوين البيانات.

كيفية العمل بفعالية مع البيانات من خلال جافاسكربت

ترشيح المصفوفة

تأخذ طريقة filter()‎ الخاصة بالنموذج الأولي للمصفوفة Array.prototype.filter()‎، وظيفة اختبار كل عنصرٍ ضمن المصفوفة، وتُعيد مصفوفةً أخرى تحتوي على القيم التي تجاوزت الاختبار، وهذا يسمح بإنشاء مجموعة بياناتٍ فرعيةٍ ذات معنى عند استخدام القائمة المنسدلة أو مرشحات النص، كما سيتمكن المستخدم من الاطلاع على المعلومات من خلال عرض أقسامٍ من البيانات، وهذا صحيحٌ أيضُا عند استخدام أبعادٍ ذات معنى ومنفصلة لمجموعة البيانات متعددة الأبعاد.

ds.filter(d => d.state === "Alabama");

// Result
[{
  state: "Alabama",
  total: 1386,
  year: 2005,
  priorYear: 1201
},{
  state: "Alabama",
  total: 989,
  year: 2006,
  priorYear: 1386
}]

ربط بيانات المصفوفة

تأخذ طريقة map()‎ الخاصة بالنموذج الأولي للمصفوفة Array.prototype.map()‎ وظيفة تمرير كل عنصرٍ في المصفوفة وإعادة مصفوفةً جديدةً بنفس عدد العناصر القديم، حيث يسمح ربط البيانات بإنشاء مجموعات بياناتٍ مرتبطة، وأحد استخدامات هذه الطريقة هو ربط بياناتٍ غامضةٍ مع بياناتٍ ذات معنى وموصوفة، أو استخدامها لأخذ قياسات وإجراء حسابات على عناصر المصفوفة لتحقيق تحليلٍ أكثر عمقًا.

 1. ربط البيانات ببيانات ذات معنى

ds.map(d => (d.state.indexOf("Alaska")) ? "Contiguous US" : "Continental US");

// Result
[
  "Contiguous US", 
  "Continental US", 
  "Contiguous US"
]

 2. ربط البيانات مع النتائج المحسوبة

ds.map(d => Math.round(((d.priorYear - d.total) / d.total) * 100));

// Result
[-13, 56, 40]

تلخيص بيانات المصفوفة

تأخذ طريقة reduce()‎الخاصة بالنموذج الأولي للمصفوفة Array.prototype.reduce()‎ وظيفة معالجة كل عنصرٍ ضمن المصفوفة وإعادة بياناتٍ مُجمعةٍ، وتُستخدم لإنجاز حساباتٍ رياضيةٍ مثل إضافة أو ضرب كل رقمٍ ضمن المصفوفة، ويُمكن استخدامها أيضًا لضم المحارف والعديد من الأمور الأخرى، ولا بد من تعلُّم هذه الدالة من خلال مثالٍ نظرًا لصعوبتها.

عليك التأكد من تلخيص البيانات عند عرضها، بحيث تُقدم فكرةً مُلخصةً للمستخدمين، ولذلك يُمكن تجميع البيانات اعتمادًا على ما يحتاجه المُستخدم حتى عند تطبيق عمليات تلخيصٍ عامة على البيانات من جهة الخادم، وبالنسبة للتطبيق الذي ننشئه، فعليّنا إضافة المجموع الكلي لكل مُدخلٍ وعرض النتيجة المُجمّعة باستخدام طريقة reduce()‎ للمرور على كل سجلٍ وإضافة القيمة الحالية إلى المجمّع، وبهذا تكون النتيجة النهائية هي المجموع الكلي لقيم كل مصفوفة.

ds.reduce((accumulator, currentValue) => 
accumulator + currentValue.total, 0);

// Result
3364

تطبيق هذه الدوال على الحالة المدروسة

حالما نحصل على البيانات سنعيّن حدثّا للزر "Get the Data" من أجل عرض المجموعة الفرعية من البيانات المناسبة، حيث توجد عدة مئاتٍ من العناصر ضمن بيانات JSON الخاصة بالتطبيق، وتتواجد الشيفرة المسؤولة عن دمج البيانات مع الزر في الملف main.js الخاص بنا:

document.getElementById("submitBtn").onclick =
  function(e){
      e.preventDefault();
      let state = document.getElementById("stateInput").value || "All"
      let year = document.getElementById("yearInput").value || "All"
      let subset = window.fetchData.filterData(year, state);
      if (subset.length == 0  )
        subset.push({'state': 'N/A', 'year': 'N/A', 'total': 'N/A'})
      document.getElementById("output").innerHTML =
      `<table class="table">
        <thead>
          <tr>
            <th scope="col">State</th>
            <th scope="col">Year</th>
            <th scope="col">Arrivals</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>${subset[0].state}</td>
            <td>${subset[0].year}</td>
            <td>${subset[0].total}</td>
          </tr>
        </tbody>
      </table>`
  }

003_javascript_taming_data_3.png

سوف يأخذ حقل الولاية أو العام القيمة الافتراضية "All" عندما يكون فارغًا. ويمكن الاطلاع على الشيفرة البرمجية التالية المُتاحة في الملف js/main.js/ وإلقاءِ نظرةٍ على الدالّة ()filterData التي نسجّل ضمنها حصة الأسد من عمليتي الترشيح والتجميع.

// with our data returned from our fetch call, we are going to 
// filter the data on the values entered in the text boxes
fetchData.filterData = function(yr, state) {
 // if "All" is entered for the year, we will filter on state 
 // and reduce the years to get a total of all years
  if (yr === "All") {
    let total = this.jsonData.filter(
     // return all the data where state
     // is equal to the input box
      dState => (dState.state === state)
        .reduce((accumulator, currentValue) => {
         // aggregate the totals for every row that has 
         // the matched value
          return accumulator + currentValue.total;
        }, 0);

    return [{'year': 'All', 'state': state, 'total': total}];
  }

  ...

 // if a specific year and state are supplied, simply
 // return the filtered subset for year and state based 
 // on the supplied values by chaining the two function
 // calls together 
  let subset = this.jsonData.filter(dYr => dYr.year === yr)
    .filter(dSt => dSt.state === state);

  return subset; 
};

// code that displays the data in the HTML table follows this. See main.js.

كما ذُكر سابقًا فإن القيمة الافتراضية لحقل الولاية أو العام هي "All" في حال بقي فارغًا، حيث تُرشح مجموعة البيانات وتُلخص قياسات جميع الأسطر حسب هذا البعد، كما ستُرشح قيمة كلٍ من العام والولاية عند إدخالهما.

لدينا الآن مثال مجرب، حيث:

  • بدأنا بمجموعة بياناتٍ أوليةٍ ذات معاملات.
  • ثم أنشأنا مجموعة بياناتٍ متعددة الأبعاد وشبه مُجمعة.
  • وبنينا نتيجةً كاملةً التكوين ديناميكيًا.

يُمكن التلاعب بالبيانات حالما تصل للمستخدم بعدة طرقٍ دون الحاجة للاتصال المتكرر بالخادم، وهذا مفيد جدًا في حال فقد المستخدم الاتصال، لأنه بذلك لن يفقد القدرة على التفاعل مع البيانات، وهذه ميزةٌ رائعةٌ خصوصًا عند بناء تطبيق ويب تقدُّمي Progressive Web App أو اختصارًا PWA، يحتاج أن يعمل دون اتصالٍ بالانترنت.

يمكن إنشاء أي تحليلٍ لأي مجموعة بيانات حالما تُحكم قبضتك على هذه الطرق الثلاث، لذلك اربط أحد الأبعاد ضمن مجموعة البيانات مع تصنيفٍ أكثر شمولية، ولخّص البيانات باستخدام reduce. تستطيع أيضًا استخدام مكتبة D3 من أجل ربط البيانات مع جداول ورسوم بيانية تسمح بتصوُّر مرئي مخصص بالكامل لهذه البيانات.

الخلاصة

يُطلعنا هذا المقال على الإمكانيات الكامنة في جافاسكربت عند التعامل مع البيانات، وكما ذُكر سابقًا فإن جافاسكربت للواجهة الأمامية ليست بديلًا لتفسير وتحويل البيانات على الخادم حيث تُنجز المهمات الصعبة، كما يجب بنفس الوقت عدم استبعادها عند معالجة مجموعات البيانات بالطريقة الصحيحة.

ترجمة -وبتصرّف- للمقال Taming Data with JavaScript لصاحبه Brian Greig.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...