رسم داده ها بر روی نقشه ها با پایتون

مدتی هست که برای یک پروژه تفریحی data science سر و کارم با رسم داده ها بر روی نقشه ها و ابزارهای مختلف این کار افتاده است. اخیرا با یک کتابخانه پایتون به نام folium آشنا شدم که به ما امکان رسم نقشه ها را با استفاده از کتابخانه leaflet.js جاوااسکریپت با API خود می دهد. برای نشان دادن قابلیت های این کتابخانه از یک دیتاست کاملا ایرانی استفاده می کنم. حدود 1.5 سال پیش شرکت اوبار که در در زمینه ارسال سفارشات و حمل بار آنلاین ( فکر کنم عمدتا بین شهری) در ایران فعالیت می کند یک مسابقه یادگیری ماشین در پلتفرم kaggle برگزار کرد که در آن به صورت خلاصه هدف آن این بود که با استفاده از یک مجموعه از اطلاعات و متغیرهای مکانی (spatial) مثل استان مبدا و مقصد و غیرمکانی مثل وزن بار و نوع وسیله قیمت بارهای حمل شده در سطح ایران به درستی پیشبینی شود تا بر اساس این پیش بینی یک سیاست گذاری ثابت برای قیمت گذاری بارها پیاده سازی شود.همانطور که گفتم در این مسابقه و دیتاست مربوط به آن انواع اطلاعات مکانی مثل عرض و طول و جغرافیایی مبدا و مقصد بار مشخص شده اند و همین این دیتاست را برای مثال این پست ایده آل می کند. (دیتاست مربوطه را می توانید از آدرس مسابقه در سایت کگل دانلود کنید)ubaar= pd.read_csv(&quotdata/train.csv&quot) ubaar.head()با این وجود دیتاست اوبار در حدود50 هزار مشاهده مجزا دارد و اگر بخواهیم همه این مشاهدات را بر روی نقشه رسم کنیم به خاطر تعاملی بودن نقشه در بارگزاری آن دچار مشکل خواهیم شد. برای همین برای سادگی تنها یک بخشی از داده ها (1000 مشاهده) را برای ادامه کار انتخاب می کنیم.sample_ubaar = ubaar.head(1000)شروع به کارمثل همیشه ابتدا نیاز به نصب کتابخانه مدنظرمان که در این جا folium است داریم:pip install foliumبعد از نصب کتابخانه نوبت به اضافه کردن کتابخانه folium و یک نقشه از ایران می سازیم. برای ساخت یک نقشه نیاز به عرض جغرافیایی و طول جغرافیایی از مرکز ایران داریم. ( اگر با مقدار ورودی پارامتر zoom_start کمی بازی کنید متوجه کاربرد آن هم می شوید)import folium iran_map = folium.Map(location = [32.4279,53.6880],zoom_start = 5) iran_mapحالا هدف ما این است که نقاط مبدا و مقصد حمل بار را بر روی نقشه ای که ایجاد کرده ایم رسم کنیم. برای این کار از کلاس ()folium.CircleMarker استفاده می کنیم که برای ما بر روی نقشه نقطه رسم می کند. برای رسم هر نقطه لازم است که عرض و جغرافیایی آن نقطه را به عنوان ورودی به این کلاس بدهیمعلاوه بر این می توانیم پارامترهای دیگری مثل رنگ، اندازه و ... هر نقطه را مشخص کنیم. به طور مثال، برای نقاط مبدا از رنگ قرمز و برای نقاط مقصد از رنگ آبی استفاده میکنیم.در اینجا بر روی دیتاست نمونه ای که ایجاد کرده ایم حلقه for می زنیم و عرض و طول جغرافیایی نقاط مبدا را به صورت مجزا به کلاس می دهیم و در انتها این نقطه را با متد به نقشه قبلی که ایجاد کرده بودیم اضافه می کنیم تا این نقاط بر روی این نقشه رسم شوند.for lat, lng in zip(sample_ubaar.sourceLatitude,sample_ubaar.sourceLongitude): folium.CircleMarker( [lat, lng], radius=2, color='red', fill=True, fill_color='darkred', fill_opacity=0.5 ).add_to(iran_map) iran_mapبه صورت مشابه همین فرآیند را برای نقاط مقصد هم اجرا می کنیم.for lat, lng in zip(sample_ubaar.destinationLatitude,sample_ubaar.destinationLongitude): folium.CircleMarker( [lat, lng], radius=2, color='blue', fill=True, fill_color='blue', fill_opacity=0.3 ).add_to(iran_map) iran_mapدر نمودارهای قبلی می توانستیم تجمع اصلی مبدا و مقصد نقاط حمل بار را مشاهده کنیم ولی اطلاعاتی در خصوص قیمت حمل بار که اهمیت بالایی دارد مشخص نبود. حالا می توانیم یک کار جالب تر هم انجام بدهیم: به جای این که اندازه هر کدام از نقاط را ثابت و برابر با هم در نظر بگیریم اندازه نقاط را بر اساس هزینه نسبی حمل بار تغییر بدهیم. برای این کار ابتدا هزینه حمل بار را نرمال سازی می کنیم و سپس هزینه حمل هر نقطه را به عنوان ورودی به پارامتر radius کلاس می دهیم و به نقشه ایران اضافه می کنیم.دقت کنید که اینجا ابتدا لازم است که دوباره نقشه را از ابتدا ایجاد کنیم چون در غیر این صورت نقشه قبلی با تمام نقاطش مورد استفاده قرار می گیرد.iran_map = folium.Map(location = [32.4279,53.6880],zoom_start = 5) برای نقاط مبدا به این صورت عمل می کنیم: for row in range(sample_ubaar.shape[0]): folium.CircleMarker( location=[sample_ubaar.iloc[row,:]['sourceLatitude'], sample_ubaar.iloc[row,:]['sourceLongitude']], radius=sample_ubaar.iloc[row,:]['price_normalized'], color='red', fill=True, fill_color='darkred', fill_opacity = 0.7 ).add_to(iran_map) iran_mapبرای نقاط مقصد هم به صورت مشابه همین فرآیند را انجام می دهیم:for row in range(sample_ubaar.shape[0]): folium.CircleMarker( location=[sample_ubaar.iloc[row,:]['destinationLatitude'], sample_ubaar.iloc[row,:]['destinationLongitude']], radius=sample_ubaar.iloc[row,:]['price_normalized'], color='blue', fill=True, fill_color='blue', fill_opacity = 0.1 ).add_to(iran_map) iran_mapرسم تک تک نقطه ها بر روی نقشه با این که به ما کمک می کند تا نقاط خاص با حجم بیشتر حمل و نقل و قیمت بیشتر در کشور را شناسایی کنیم اما این به ما در خصوص مهم ترین خوشه های مکانی با حجم بالای حمل و نقل یک دید کلی و جامع نمی دهد.حالا می توانیم یک گام فراتر برویم و برای این که این دید کلی را هم داشته باشیم می توانیم از یک قابلیت فوق العاده دیگر کتابخانه folium استفاده کنیم و نقشه های گرمایی را به جای نقاط بر روی نقشه های جغرافیایی نشان دهیم. برای رسم نقشه های گرمایی در folium از کلاس ()HeatMap استفاده می کنیم و علاوه بر عرض و طول جغرافیایی وزن هر نقطه که در این جا قیمت است را به عنوان ورودی به آن می دهیم. یک مزیت دیگر استفاده از نقشه های گرمایی در اینجا این است که می توانیم تعداد مشاهدات بیشتری را بر روی نقشه رسم کنیم. برای همین در این مثال به جای 1000 مشاهده 20000 مشاهده را رسم می کنم.iran_map = folium.Map(location = [32.4279,53.6880],zoom_start = 5) from folium.plugins import HeatMap iran_map = folium.Map(location = [32.4279,53.6880],zoom_start = 5) iran_map ubaar['price_normalized'] = (ubaar['price'] - ubaar['price'].min())/ ubaar['price'].max() HeatMap( data=list(zip(ubaar.head(20000)['sourceLatitude'], ubaar.head(20000)['sourceLongitude'], ubaar.head(20000)['price_normalized']) ) , radius = 12 ).add_to(iran_map) iran_map