أدوات Matplotlib لذكاء بيانات PlatoBlockchain. البحث العمودي. منظمة العفو الدولية.

أدوات ماتبلوتليب

Matplotlib ليس مخصصًا للمخططات الثابتة فقط. بينما يتم إنشاء واجهات المستخدم الرسومية عادةً باستخدام مكتبات وأطر عمل واجهة المستخدم الرسومية مثل باي كيوت, تكينتر, كيفي و wxPythonوبينما تتمتع Python بتكامل ممتاز مع PyQt وTkinter و wxPython – ليست هناك حاجة لاستخدام أي منها لبعض وظائف واجهة المستخدم الرسومية الأساسية، من خلال أدوات ماتبلوتليب.

matplotlib.widgets تحتوي الوحدة على عدة فئات، بما في ذلك AxesWidget، منها Buttons, CheckButtons, Sliders, TextBoxes، الخ مشتقة. كل هذه تقبل Axes تتم إضافتها باعتبارها الوسيطة الإنشائية الإلزامية الوحيدة والوحيدة، ويجب تعيين موضعها يدويًا. الشيء الذي يجب ملاحظته هو أن القطعة هي المحاور، لذلك ستقوم بإنشاء Axes مثيل لكل القطعة.

شيء آخر يجب ملاحظته هو ذلك يجب عليك الاحتفاظ بالمراجع إلى الحاجيات وإلا فقد يتم جمع القمامة.

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

ملحوظة: ليس المقصود من Matplotlib أن يتم استخدامه لإنشاء واجهة المستخدم الرسومية عالية الجودة، ولا الأنظمة سهلة الاستخدام. هذه الأدوات بدائية ولا تبدو رائعة حقًا ولها وظائف محدودة. لقد كان المقصود منها أن تكون وسيلة لإنشاء نماذج أولية واختبار الأشياء، بدلاً من شحنها فعليًا.

إذا كنت قد عملت مع PyQt من قبل - فقد تلاحظ أن البنية العامة وطريقة إضافة هذه الأدوات، بالإضافة إلى ربطها بمعالجات الأحداث مألوفة إلى حد ما.

إضافة الأزرار

لنبدأ بـ أزرار - في matplotlib.widgets تحدد الوحدة أ Button فصل. للاتصال به نسميه on_clicked() وظيفة، والتي تنفذ الوظيفة التي نوفرها. بمجرد اكتشاف النقرة، يتم تنفيذ الوظيفة.

أثناء إنشاء الزر، نقوم بتعيين Axes إليها، وتستخدم لتحديد المواقع. يمكننا أيضًا المرور في label في ذلك الوقت، لإضافة بعض النص والتعليق عليه للمستخدم. ال color و hovercolor تحدد الوسائط لون الزر قبل وبعد التمرير فوقه.

نظرًا لأننا نهتم بالموضع والمساحة لجميع عناصر واجهة المستخدم، فلنقم بإنشاء ملف Figure و Axes، واسمح لبعض المسافات في الأسفل لإضافة زر، ورسم مخطط مبعثر فارغ. وبعد ذلك، سوف نقوم بتحديد EventHandler فئة، لديها طريقة واحدة add_random(). تقوم الطريقة بإنشاء رقمين عشوائيين، وترسم علامة لهما على Axes لقد أنشأنا من قبل والمكالمات plt.draw()، الذي يعيد رسم Figure. عند تحديث قطع الأراضي، سيتعين علينا الاتصال دائمًا plt.draw() مرة أخرى لتحديثه فعليًا:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Button fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.2)
plot = ax.scatter([], []) class EventHandler: def add_random(self, event): x = np.random.randint(0, 100) y = np.random.randint(0, 100) ax.scatter(x, y) plt.draw() # Axes for the Button and positioning
# xposition and yposition in percentages, width, height
button_ax = plt.axes([0.7, 0.05, 0.2, 0.07])
# Create Button and assign it to `button_ax` with label
button = Button(button_ax, 'Add Random', color='green', hovercolor='red')
# On a detected click, execute add_random()
button.on_clicked(EventHandler().add_random) plt.show()

ينتج عن هذا ملف Figure، مع فارغة Axes بداخله وزر في الزاوية اليمنى العليا من الشاشة بمفرده Axes:

أدوات Matplotlib لذكاء بيانات PlatoBlockchain. البحث العمودي. منظمة العفو الدولية.

وبعد الضغط على الزر عشرات المرات، لدينا ax سيتم ملؤها بعلامات عشوائية:

أدوات Matplotlib لذكاء بيانات PlatoBlockchain. البحث العمودي. منظمة العفو الدولية.

وبعبارات أكثر عملية، يمكننا إنشاء دورة من الميزات التي سيتم رسمها عند الضغط على كل زر. وهذا يتطلب بعض التعديلات على EventHandlerبالإضافة إلى زر آخر للرجوع خلال تلك الدورة.

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

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

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.widgets import Button fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.2) df = pd.read_csv('winequality-red.csv')
plot = ax.scatter([], []) class EventHandler: i = 0 # Find and plot next feature, re-draw the Axes def next_feature(self, event): # If the counter is at the end of the columns # Revert it back to 0 to cycle through again if self.i >= len(df.columns): self.i = 0 # Clear Axes from last plot ax.cla() # Plot a feature against a feature located on the `i` column ax.scatter(df['alcohol'], df.iloc[:,self.i]) # Set labels ax.set_xlabel('Alcohol') ax.set_ylabel(df.columns[self.i]) # Increment i self.i += 1 # Update Figure plt.draw() def previous_feature(self, event): # If the counter is at the start of the columns # Revert it back to the last column to cycle through if self.i <= 0: self.i = len(df.columns)-1 ax.cla() ax.scatter(df['alcohol'], df.iloc[:,self.i]) ax.set_xlabel('Alcohol') ax.set_ylabel(df.columns[self.i]) self.i -= 1 plt.draw() # Add buttons
button1_ax = plt.axes([0.7, 0.02, 0.2, 0.07])
next_button = Button(button1_ax, 'Next Feature')
next_button.on_clicked(EventHandler().next_feature) button2_ax = plt.axes([0.45, 0.02, 0.2, 0.07])
previous_button = Button(button2_ax, 'Previous Feature')
previous_button.on_clicked(EventHandler().previous_feature) plt.show()

EventHandler الفصل لديه الآن طريقتان - next_feature() و previous_feature(). كل من هذه تحقق ما إذا كان العداد i قد وصل إلى نهاية قائمة الأعمدة أو بدايتها - ولتجنب ذلك IndexError، نقوم بإعادة تعيين الفهرس إلى القيمة المعاكسة ومحاكاة a دورة. الذهاب أدناه 0 سوف يعيدنا إلى النهاية من قائمة الأعمدة، والذهاب فوق العمود الأخير سيعيدنا إلى العمود الأول.

بعد التأكد من مكان تواجدنا - نحن نظف ال Axes، نظرًا لأننا سنقوم بالتخطيط مرة أخرى فوق قطعة أرض موجودة دون مسحها cla() (clوسدادة للأذن axes). يمكنك بدلاً من ذلك تجميع علاقات الميزات أيضًا، من خلال التخطيط فوق بعضها البعض واستخدام cla() بيان عند إعادة ضبط الفهرس في نهاية/بداية الدورة.

بعد مسح ملف Axes - لدينا لوحة قماشية نظيفة لنرسم عليها ax.scatter() وظيفة. في هذا المثال، الميزة الثابتة هي كحول، لذلك فهو حاضر في كل الأوقات. وتختلف الميزة الأخرى، ويمكن الوصول إليها من خلال iloc[]، مروراً بفهرس العمود. هذا يعود أ Series التي يمكننا استخدامها في هذه المؤامرة. وبالمثل، يمكننا الوصول أسماء الأعمدة من خلال فهرسهم أيضًا - df.columns[index]، والذي يُستخدم لتعيين تسمية المحور Y.

أخيرًا، نقوم بزيادة/تقليل العداد والاتصال plt.draw() لتحديث Figure:

أدوات Matplotlib لذكاء بيانات PlatoBlockchain. البحث العمودي. منظمة العفو الدولية.

بمجرد النقر فوق الميزة التالية الزر، سيتم رسم الميزة التالية في قائمة الأعمدة مقابلها كحول، و Figure سيتم تحديثها بشكل مناسب - التسميات والعلامات والمقياس. الشيء نفسه يحدث في الاتجاه المعاكس - الميزة السابقة سوف نجتاز القائمة في الاتجاه المعاكس، مما يسمح لنا بالدوران ذهابًا وإيابًا، مع آلية أمان تعيد ضبط العداد في كل مرة نصل إلى نهاية الدورة أو بدايتها.

إضافة أزرار الراديو وخانات الاختيار

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

ملحوظة: هناك قدرة محدودة جدًا على التحقق مما إذا كانت خانة الاختيار موجودة أم لا on or خصم. في الواقع، لا يوجد شيء خارج الصندوق. يمكنك فقط التحقق مما إذا كان المربع موجودًا أم لا مضغوط or ليس، مما يشكل قيدًا خطيرًا على كيفية استخدامه نظرًا لأنه ليس لدينا أي فكرة عن الحالة التي كان عليها قبل ذلك. البديل الوحيد هو الاحتفاظ بالعداد/التحقق الخاص بك فيما يتعلق بالحالة الحالية للمربع باستخدام قيمة منطقية، وتغيير المنطق بناءً على ذلك.

سيسمح لك هذا، على سبيل المثال، بإضافة مربع اختيار لكل منها حجة التخصيص لمؤامرة معينة، مما يسمح للمستخدم بتعيينها True or False (محدد أو غير محدد)، أو أي تعيين آخر غير متعارض بناءً على هذه الحالات.

ومع ذلك، نظرًا لأن واجهة برمجة التطبيقات (API) محدودة في حد ذاتها، فسنقتصر على الاستخدام المقصود أيضًا - تشغيل الأشياء وإيقاف تشغيلها. سيكون لدينا ميزتان يمكننا تحويلهما on و خصم عبر خانة الاختيار. لاحظ أنه حتى هذه الوظيفة تقتصر على الكائنات التي يمكنك التحقق مما إذا كانت مرئية أم لا.

من ناحية أخرى، لا نريد السماح للمستخدم بتطبيق مقياسين في وقت واحد، أو تعيين حدي X في وقت واحد، حيث سيتم تطبيق العبارة المسماة ثانية فقط في التسلسل. لهذه – سنستخدم أزرار الراديو.

دعونا نضيف اثنين من أزرار الراديو للسماح للمستخدم بتحديد نطاق المحور عبر اثنين من أزرار الراديو، ولكن أيضًا السماح لهم بتشغيل تصورات الميزات on و خصم:

import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.widgets import CheckButtons
from matplotlib.widgets import RadioButtons fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.2) df = pd.read_csv('winequality-red.csv') # Plot two line plots for two features, and turn them invisible
line1, = ax.plot(df['fixed acidity'], visible=False)
line2, = ax.plot(df['citric acid'], visible=False) class EventHandler: # set_range handler def set_range(label): if (label == 'Small Range'): ax.set_xlim(0, 1600) ax.set_ylim(0, 25) else: ax.set_xlim(0, 1600) ax.set_ylim(0, 50) plt.draw() # Turn off, if on, and on if off def apply_features(label): if (label == 'Fixed Acidity'): line1.set_visible(not line1.get_visible()) elif (label == 'Citric Acid'): line2.set_visible(not line2.get_visible()) plt.draw() # Add radio buttons and checkboxes
ranges_ax = plt.axes([0.7, 0.02, 0.2, 0.1])
range_radio_buttons = RadioButtons(ranges_ax, ('Small Range', 'Large Range'))
range_radio_buttons.on_clicked(EventHandler.set_range) checkboxes_ax = plt.axes([0.4, 0.02, 0.25, 0.1])
checkboxes = CheckButtons(checkboxes_ax, ('Fixed Acidity', 'Citric Acid'))
checkboxes.on_clicked(EventHandler.apply_features) plt.show()

مرة أخرى، لدينا طريقتان في EventHandler() فصل - set_range() و apply_features(). set_range() تقوم الطريقة بتعيين النطاق إما على "صغير" أو "كبير"، عن طريق ضبط Axesحدود X وY. ال apply_features() تعمل الوظيفة على تغيير visible مجال قطع الخط التي قمنا بها سابقًا، بناءً على حالتها الحالية visible حالة. لو visible == True، نقوم بإيقاف تشغيل Line Plot، والعكس صحيح.

علينا أن نعتمد على القدرة المضمنة للتحقق من رؤية مخططات الخط، حيث لا يمكننا التحقق مما إذا كان مربع الاختيار قد تم تحديده أم لا من قبل. يمكن محاكاة هذه القدرة نفسها باستخدام ملف status منطقية في نطاق EventHandler() الطبقة، والتي تم تعيينها ل True و False في كل نقرة، بالنسبة لأنواع المخططات التي لا تدعم التحقق مما إذا كانت مرئية خارج الصندوق.

يؤدي تشغيل هذا الرمز إلى أ Figure مع مجموعتين من الأزرار في الأسفل. إذا قمنا بتحديد كلا مربعي الاختيار، فسوف تظهر كلا المخططات الخطية:

أدوات Matplotlib لذكاء بيانات PlatoBlockchain. البحث العمودي. منظمة العفو الدولية.

يمكننا إيقاف تشغيلها بشكل فردي:

أدوات Matplotlib لذكاء بيانات PlatoBlockchain. البحث العمودي. منظمة العفو الدولية.

ويمكننا تغيير نطاق Axes عبر أزرار الراديو:

أدوات Matplotlib لذكاء بيانات PlatoBlockchain. البحث العمودي. منظمة العفو الدولية.

إضافة مربعات النص

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

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

import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.widgets import TextBox fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.2) df = pd.read_csv('winequality-red.csv') class EventHandler: def submit(feature_name): if feature_name != "" or feature_name != None: if feature_name in df: ax.cla() ax.plot(df[feature_name]) else: if len(textbox_ax.texts) > 2: del textbox_ax.texts[-1] textbox_ax.text(-2, 0.4, feature_name + ' was not found.') plt.draw() textbox_ax = plt.axes([0.7, 0.02, 0.2, 0.1])
textbox = TextBox(textbox_ax, 'Feature Name')
textbox.on_submit(EventHandler.submit) plt.show()

لدينا فحص بسيط لمعرفة ما إذا كان المقدم feature_name فارغة أو None، وفي هذه الحالة، نحن لا نفعل أي شيء. إذا لم يكن الأمر كذلك، فإننا نتحقق مما إذا كان feature_name موجود في DataFrame، مع إرفاق رسالة تفيد بعدم العثور على الميزة في حالة عدم وجودها. ولكن قبل إرفاق النص، علينا التأكد من إزالة الرسالة السابقة، حتى لا تتداخل الرسالة الجديدة معها. ال axes.texts الملكية هي قائمة بجميع Text الحالات على Axes. منذ Axes بالفعل Text على سبيل المثال، تنتمي إلى لدينا TextBox، لا نريد إزالة أي شيء إذا كان هناك 2 أو أقل Text الحالات الموجودة - رسالة الخطأ و TextBox ضع الكلمة المناسبة.

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

إذا كانت الميزة is موجودة في DataFrameومع ذلك، فإننا نقوم بمسح Axes ورسمها:

أدوات Matplotlib لذكاء بيانات PlatoBlockchain. البحث العمودي. منظمة العفو الدولية.

أدوات Matplotlib لذكاء بيانات PlatoBlockchain. البحث العمودي. منظمة العفو الدولية.

أدوات Matplotlib لذكاء بيانات PlatoBlockchain. البحث العمودي. منظمة العفو الدولية.

إضافة محددات النطاق

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

لإضافة أ محدد النطاق، لسنا بحاجة إلى تكريس جديد بالكامل Axes من أجل ذلك – يمكننا أن نربطه بواحد موجود، وهو أمر منطقي للغاية. عند إنشاء أ SpanSelector، نحن توريد Axes ينتمي إليه، بالإضافة إلى معالج الحدث، متبوعًا به 'horizontal' or 'vertical'، الذي يدور Axes و محدد النطاق على حد سواء.

useblit عادةً ما يتم تعيين الوسيطة على True لأنه يعزز الأداء في معظم الواجهات الخلفية. بالإضافة إلى ذلك، أضفنا بعض خصائص التصميم، مثل ضبط alpha للمستطيل الذي تم إنشاؤه كمحدد Span ل 0.5 و facecolor إلى لطيفة tab:blue:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.widgets import SpanSelector
from matplotlib.widgets import Button fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.2) df = pd.read_csv('AmesHousing.csv') ax.scatter(x = df['Year Built'], y = df['Total Bsmt SF'], alpha = 0.6) class EventHandler: def select_horizontal(x, y): ax.set_xlim(x, y) plt.draw() def reset(self): ax.set_xlim(df['Year Built'].min(), df['Year Built'].max()) plt.draw span_horizontal = SpanSelector(ax, EventHandler.select_horizontal, 'horizontal', useblit=True, rectprops=dict(alpha=0.5, facecolor='tab:blue')) button_ax = plt.axes([0.7, 0.02, 0.2, 0.07])
button = Button(button_ax, 'Reset')
button.on_clicked(EventHandler.reset) plt.show()

يؤدي تشغيل هذا إلى إنشاء مخطط يمكننا من خلاله تحديد الامتدادات وتكبيرها عن طريق تعيين حدود المحاور على القيم المقدمة:

أدوات Matplotlib لذكاء بيانات PlatoBlockchain. البحث العمودي. منظمة العفو الدولية.

أدوات Matplotlib لذكاء بيانات PlatoBlockchain. البحث العمودي. منظمة العفو الدولية.

أدوات Matplotlib لذكاء بيانات PlatoBlockchain. البحث العمودي. منظمة العفو الدولية.

إضافة المتزلجون

المتزلجون السماح للمستخدمين بالاختيار بين العديد من القيم بشكل حدسي عن طريق تحريك العلامة واختيار القيمة. عادةً، يتم استخدام أشرطة التمرير لتحديث بعض القيم بشكل مستمر على قطعة أرض، مثل نطاقها or حتى الميزة. على سبيل المثال، يمكنك ضبط قيمة ثابت من خلال شريط التمرير، والذي يؤثر بدوره على دالة تعتمد على هذا الثابت.

لنكتب نصًا يسمح لنا بتغيير حدود المحور Y وX، من خلال شريط التمرير، والذي سيسمح لنا بتغيير المنظور الذي نعرض منه بياناتنا:

import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.2, left=0.2) df = pd.read_csv('winequality-red.csv')
plot, = ax.plot(df['volatile acidity']) class EventHandler: def update(val): ax.set_ylim(0, yslider.val) ax.set_xlim(0, xslider.val) plt.draw() xslider_ax = plt.axes([0.35, 0.03, 0.5, 0.07])
xslider = Slider( ax=xslider_ax, label="X-limit", valmin=0, valmax=len(df['volatile acidity']), valinit=len(df['volatile acidity']), orientation="horizontal"
) yslider_ax = plt.axes([0.03, 0.2, 0.07, 0.5])
yslider = Slider( ax=yslider_ax, label="Y-limit", valmin=0, valmax=3, valinit=1.5, orientation="vertical"
) xslider.on_changed(EventHandler.update)
yslider.on_changed(EventHandler.update) plt.show()

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

وأخيرا، Sliderيمكن توجيه s أفقيًا أو رأسيًا. نظرًا لأنه من المفترض أن يتم تحديثها باستمرار عبر تمرير الماوس - فإن on_changed() تُستخدم الوظيفة لتحفيز الاستجابة عندما يقدم المستخدم مدخلات. لقد قمنا بتعديل EventHandler فئة مع update() وظيفة تقوم ببساطة بضبط قيم حدود X وY بناءً على value من المتزلجون المعنيين.

سيؤدي تشغيل هذا الكود إلى إنتاج مخطط يحتوي على شريطين تمرير، يمكننا استخدامهما لتغيير نطاق الملف Axes:

أدوات Matplotlib لذكاء بيانات PlatoBlockchain. البحث العمودي. منظمة العفو الدولية.

أدوات Matplotlib لذكاء بيانات PlatoBlockchain. البحث العمودي. منظمة العفو الدولية.

أدوات Matplotlib لذكاء بيانات PlatoBlockchain. البحث العمودي. منظمة العفو الدولية.

الطابع الزمني:

اكثر من ستاكابوز