Python ile Keşifsel Veri Analizi

Enes Eren
9 min readMar 28, 2023

Python ile Pandas, Matplotlib, Numpy ve Seaborn paketlerini kullanarak bir veri seti üzerinde keşifsel veri analizi, veri doğrulama, veri özetleme, veri temizleme vb. aşamaları uygulayacağız.

Keşifsel veri analizi, veriden çıkarım elde etmek için veriyi inceleme ve temizleme sürecidir.

Paketleri ve Veriyi İçe Aktarma

import pandas as pd
import seaborn as sns
import numpy as np
from matplotlib import pyplot as plt

unemployment = pd.read_csv('unemployment.csv')
planes = pd.read_csv('planes.csv')

Aktarma işlemlerini yaptıktan sonra yapacağımız ilk iş üzerinde çalışacağımız veri setine bir göz atmak olacaktır.

İlk Bakış

Veri seti ile ilgili hızlıca bilgi sahibi olmak olmak için veri setine bir göz atalım.

print(unemployment.head())
  country_code          country_name      continent   2010   2011  ...   2017   2018   2019   2020   2021
0 AFG Afghanistan Asia 11.35 11.05 ... 11.18 11.15 11.22 11.71 13.28
1 AGO Angola Africa 9.43 7.36 ... 7.41 7.42 7.42 8.33 8.53
2 ALB Albania Europe 14.09 13.48 ... 13.62 12.30 11.47 13.33 11.82
3 ARE United Arab Emirates Asia 2.48 2.30 ... 2.46 2.35 2.23 3.19 3.36
4 ARG Argentina South America 7.71 7.18 ... 8.35 9.22 9.84 11.46 10.90
[5 rows x 15 columns]

Head fonksiyonu ile birlikte bir DataFrame nesnesinin ilk birkaç satırını bir tablo olarak görüntüleyebiliriz. Veri setimiz 15 sütundan oluşmakla birlikte bu sütunların üçü ülke kodu, ülke adı ve ülkenin bulunduğu kıta ile ilgili metinsel değerler içermektedir. Kalan sütunlar ise bu ülkelerin yıllara göre işsizlik oranlarını vermektedir ve bu değerler sayısaldır.

Burada satır ve sütunlar ile ilgili bilgi sahibi olduktan sonra eksik değerlere göz atabiliriz.

print(unemployment.info())
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 182 entries, 0 to 181
Data columns (total 15 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 country_code 182 non-null object
1 country_name 182 non-null object
2 continent 177 non-null object
3 2010 182 non-null float64
4 2011 182 non-null float64
5 2012 182 non-null float64
6 2013 182 non-null float64
7 2014 182 non-null float64
8 2015 182 non-null float64
9 2016 182 non-null float64
10 2017 182 non-null float64
11 2018 182 non-null float64
12 2019 182 non-null float64
13 2020 182 non-null float64
14 2021 182 non-null float64
dtypes: float64(12), object(3)

Info fonksiyonu ise sütunlardaki veri tipi ve eksik değerler ile ilgili bilgi verir. Veri setimizin continent sütunu 5 satır eksik değer içermektedir. Yani 5 ülkenin kıta bilgisi bulunmamaktadır. Onun haricinde sütunların veri tipleri doğru ayarlanmıştır.

Kategorik sütunlar ile ilgili daha detaylı bilgi almak için value_counts fonksiyonunu kullanabiliriz.

print(unemployment.continent.value_counts())
Africa           53
Asia 47
Europe 39
North America 18
South America 12
Oceania 8
Name: continent, dtype: int64

Örneğin burada kategorik sütun olan continent sütunundaki değerlerin sayısı ile ilgili bilgi aldık. Veri setimizdeki ülkelerin 53'ü Afrika, 47'si Asya ve 39'u da Avrupa kıtalarından oluşmakla birlikte diğer kıtalarla ilgili bilgiler bulunmaktadır.

Sayısal değerler içeren sütunlar ile ilgili bilgi almak için ise describe fonksiyonunu kullanabiliriz.

print(unemployment.describe())
              2010     2011     2012     2013     2014  ...     2017     2018     2019     2020     2021
count 182.000 182.000 182.000 182.000 182.000 ... 182.000 182.000 182.000 182.000 182.000
mean 8.409 8.315 8.318 8.345 8.180 ... 7.669 7.426 7.244 8.421 8.391
std 6.249 6.267 6.367 6.416 6.284 ... 5.902 5.819 5.697 6.041 6.067
min 0.450 0.320 0.480 0.250 0.200 ... 0.140 0.110 0.100 0.210 0.260
25% 4.015 3.775 3.743 3.692 3.625 ... 3.690 3.625 3.487 4.285 4.335
50% 6.965 6.805 6.690 6.395 6.450 ... 5.650 5.375 5.240 6.695 6.425
75% 10.957 11.045 11.285 11.310 10.695 ... 10.315 9.258 9.445 11.155 10.840
max 32.020 31.380 31.020 29.000 28.030 ... 27.040 26.910 28.470 29.220 33.560

[8 rows x 12 columns]

Bu fonksiyon bize sayısal sütunlar ile ilgili ortalama, standart sapma, minimum gibi özet fonksiyonlarının değerlerini verecektir.

Veri seti ile ilgili bir görselleştirme yaparak da hızlıca bilgi alabiliriz. Örneğin 2021 sütununu bir histogram grafiği ile görselleştirelim.

sns.histplot(data=unemployment, x='2021', binwidth=1)
plt.show()

Görünüşe göre 2021 yılındaki işsizlik oranları %3 ve %8 arasında birçok ülke tarafından görülmektedir. Bununla birlikte %25 ve %35 arasında işsizlik oranına sahip ülkeler de bulunmaktadır.

Veri Doğrulama

Verinin doğruluğu, analizin çok ileri aşamalara gitmeden önce yapılması gereken bir aşamadır. Aksi takdirde bu aşamadan önce yapacağımız analizlerde yanlış sonuçlar elde etme olasılığı çok yüksektir.

Veri tiplerini görmek için info fonksiyonunu kullanmıştık, ayrıca dtypes metodu da bize sütunların veri tipleri ile ilgili bilgi verir. Eğer bir sütunun veri tipini değiştirmek istersek;

unemployment["2019"] = unemployment["2019"].astype('float64')

Burada 2019 sütununu float64 veri tipine dönüştürdük.

Kategorik değerleri doğrulamak için isin (“is includes”) fonksiyonu kullanılabilir.

not_oceania = ~unemployment["continent"].isin(["Oceania"])
print(not_oceania)
0       True
1 True
2 True
3 True
4 True
...
177 False
178 True
179 True
180 True
181 True
Name: continent, Length: 182, dtype: bool

Örneğin burada continent sütununda Oceania değerini içermeyen değerleri bir boolean çıktı olarak aldık. Eğer içeren olarak düzenlemek isteseydik baştaki ~ (yaklaşık) işaretini kaldırmamız gerekirdi.

Veri seti üzerinde kullanırsak;

print(unemployment[not_oceania])
        country_code          country_name      continent   2010   2011  ...   2017   2018   2019   2020   2021
0 AFG Afghanistan Asia 11.35 11.05 ... 11.18 11.15 11.22 11.71 13.28
1 AGO Angola Africa 9.43 7.36 ... 7.41 7.42 7.42 8.33 8.53
2 ALB Albania Europe 14.09 13.48 ... 13.62 12.30 11.47 13.33 11.82
3 ARE United Arab Emirates Asia 2.48 2.30 ... 2.46 2.35 2.23 3.19 3.36
4 ARG Argentina South America 7.71 7.18 ... 8.35 9.22 9.84 11.46 10.90
.. ... ... ... ... ... ... ... ... ... ... ...
175 VNM Vietnam Asia 1.11 1.00 ... 1.87 1.16 2.04 2.39 2.17
178 YEM Yemen, Rep. Asia 12.83 13.23 ... 13.30 13.15 13.06 13.39 13.57
179 ZAF South Africa Africa 24.68 24.64 ... 27.04 26.91 28.47 29.22 33.56
180 ZMB Zambia Africa 13.19 10.55 ... 11.63 12.01 12.52 12.85 13.03
181 ZWE Zimbabwe Africa 5.21 5.37 ... 4.78 4.80 4.83 5.35 5.17

[174 rows x 15 columns]

Böylece continent sütununu Oceania değerini içermeyenler olarak filtreledik.

Veri Özetleme

group_by fonksiyonunu ile özet fonksiyonlarını kullanarak veri ile ilgili özet değerler elde edebiliriz.

print(unemployment.agg(['mean','std']))
          2010   2011   2012   2013   2014  ...   2017   2018   2019   2020   2021
mean 8.409 8.315 8.318 8.345 8.180 ... 7.669 7.426 7.244 8.421 8.391
std 6.249 6.267 6.367 6.416 6.284 ... 5.902 5.819 5.697 6.041 6.067

[2 rows x 12 columns]

Örneğin burada yıllara göre işsizlik oranlarının ortalama ve standart sapma değerlerini aldık.

print(unemployment.groupby('continent').agg(['mean','std']))
                     2010           2011           2012  ...   2019    2020           2021       
mean std mean std mean ... std mean std mean std
continent ...
Africa 9.344 7.411 9.369 7.402 9.241 ... 7.455 10.308 7.928 10.474 8.132
Asia 6.241 5.146 5.942 4.780 5.835 ... 5.254 7.012 5.700 6.906 5.415
Europe 11.008 6.392 10.948 6.540 11.326 ... 4.125 7.471 4.071 7.415 3.948
North America 8.663 5.116 8.563 5.377 8.449 ... 4.770 9.298 4.963 9.155 5.076
Oceania 3.623 2.055 3.647 2.008 4.104 ... 2.369 4.274 2.617 4.280 2.672
South America 6.871 2.807 6.518 2.802 6.411 ... 3.380 10.275 3.411 9.924 3.612

[6 rows x 24 columns]

Burada ise aynı değerleri her bir kıta için aldık.

continent_summary = unemployment.groupby("continent").agg(
# Create the mean_rate_2021 column
mean_rate_2021=("2021", "mean"),
# Create the std_rate_2021 column
std_rate_2021=("2021", "std")
)

print(continent_summary)
                   mean_rate_2021  std_rate_2021
continent
Africa 10.474 8.132
Asia 6.906 5.415
Europe 7.415 3.948
North America 9.155 5.076
Oceania 4.280 2.672
South America 9.924 3.612

Burada ise her bir kıtanın 2021 yılındaki işsizlik oranlarının ortalama ve standart sapma değerlerini aldık.

Eksik Veri

Analize başlamadan önce veri setindeki eksik veriyi tespit edip bazı yöntemler uygulamamız gerekir. Bu yöntemler eğer eksik veri oranı toplam verinin az bir kısmı ise o veriyi çıkarmak olabilir. Ya da bu eksik veriye var olan verilerden doldurma yapabiliriz. Eğer eksik veri ile analiz yapılırsa analizin yanlış sonuçlar verme olasılığı artar.

print(planes.isna().sum())

isna fonksiyonu, null (eksik) değerler için True dönen bir fonksiyondur. Eğer bu True değerleri toplarsak sütunlardaki toplam eksik değerlerin sayısını tespit edebiliriz.

    Airline            427
Date_of_Journey 322
Source 187
Destination 347
Route 256
Dep_Time 260
Arrival_Time 194
Duration 214
Total_Stops 212
Additional_Info 589
Price 616
dtype: int64

Burada çözüm olarak eksik değerleri veri setinden çıkarmak iyi bir çözüm olabilir. Bunun için bir aralık belirleyelim.

threshold = len(planes) * 0.05

%5'lik bir aralık belirledik.

cols_to_drop = planes.columns[planes.isna().sum() <= threshold]

Burada ise sütunların eksik değerleri eğer toplam veri seti içerisinde %5 veya daha az ise o sütunu seç şeklinde bir filtre oluşturduk.

planes.dropna(subset=cols_to_drop, inplace=True)

Burada da oluşturduğumuz filtreyi dropna fonksiyonu ile kullanarak %5 ve daha az eksik değer içeren sütunlardaki eksik değerleri çıkardık.

print(planes.isna().sum())
    Airline              0
Date_of_Journey 0
Source 0
Destination 0
Route 0
Dep_Time 0
Arrival_Time 0
Duration 0
Total_Stops 0
Additional_Info 300
Price 368
dtype: int64

Burada Price ve Additional_Info sütunları hariç bütün sütunlardaki eksik değerlerin bulunduğu satırlar silindi. Silme işlemi sadece bu DataFrame nesnesi için geçerlidir. Orijinal veri setini etkilemez. Ayrıca eğer bir satırda eksik değer var ise ve o satır silindiyse, o satırdaki var olan değerler de bu süreçte kaybolacaktır. Price ve Additional_Info sütunlarındaki satır azalması bundan kaynaklıdır.

Price sütunundaki sayısal değerler için o sütunun medyan değerini eksik değerler yerine kullanabiliriz.

airline_prices = planes.groupby("Airline")["Price"].median()

print(airline_prices)
    Airline
Air Asia 5192.0
Air India 9443.0
GoAir 5003.5
IndiGo 5054.0
Jet Airways 11507.0
Multiple carriers 10197.0
SpiceJet 3873.0
Vistara 8028.0
Name: Price, dtype: float64

Burada her bir havayolu şirketinin fiyatlarının medyan değerini aldık.

prices_dict = airline_prices.to_dict()

planes["Price"] = planes["Price"].fillna(planes["Airline"].map(prices_dict))

print(planes.isna().sum())

Burada ise bu değerleri bir sözlüğe dönüştürdük ve fillna fonksiyonu ile birlikte aynı havayolu şirketine karşılık gelen eksik değeri medyan değeri ile doldurduk.

    Airline            0
Date_of_Journey 0
Source 0
Destination 0
Route 0
Dep_Time 0
Arrival_Time 0
Duration 0
Total_Stops 0
Price 0
dtype: int64

Kategorik Veri Analizi

Kategorik veri, metinsel değer içeren veri tipleridir. DataFrame nesneleri bu değerleri “object” veri tipinde saklamaktadır. For döngüsü ile kategorik sütunlardaki özel metinsel değerlere bir göz atalım;

non_numeric = planes.select_dtypes("object")

for col in non_numeric.columns:

print(f"Number of unique values in {col} column: ", non_numeric[col].nunique())
    Number of unique values in Airline column:  8
Number of unique values in Date_of_Journey column: 44
Number of unique values in Source column: 5
Number of unique values in Destination column: 6
Number of unique values in Route column: 122
Number of unique values in Dep_Time column: 218
Number of unique values in Duration column: 362
Number of unique values in Total_Stops column: 5
Number of unique values in Additional_Info column: 9

Duration sütunu birbirinden farklı 362 özel değer içermektedir. Bu uçuş sürelerini bir aralığa göre kategorize edebiliriz.

Aralıkları oluşturalım.

flight_categories = ["Short-haul", "Medium", "Long-haul"]

short_flights = "0h|1h|2h|3h|4h"

medium_flights = "5h|6h|7h|8h|9h"

long_flights = "10h|11h|12h|13h|14h|15h|16h"

conditions = [
(planes["Duration"].str.contains(short_flights)),
(planes["Duration"].str.contains(medium_flights)),
(planes["Duration"].str.contains(long_flights))
]

Bu aralıkları numpy yardımı ile düzenleyelim ve seaborn ile bir bar grafiği çizelim.

planes["Duration_Category"] = np.select(conditions, 
flight_categories,
default="Extreme duration")

sns.countplot(data=planes, x="Duration_Category")
plt.show()

Uçuşların büyük bir bölümü kısa mesafeden oluşmaktadır. Kısa mesafeyi 0–4 saat arası olarak ayarlamıştık. Ayrıca 10 saat ve üzeri herhangi bir uçuş bulunmamaktadır.

Sayısal Veri Analizi

Örneğin duration sütunundaki kategorik değerleri sayısal değerlere çevirelim.

Bunun için önce bu değerlere bir göz atalım.

print(planes["Duration"].head())
    0                  19.0h
1 5.416666666666667h
2 4.75h
3 2.4166666666666665h
4 15.5h
Name: Duration, dtype: object

Bu değerlerdeki h harfini kaldıralım.

planes["Duration"] = planes["Duration"].str.replace("h", "")

Ardından veri tipini float olarak ayarlayalım.

planes["Duration"] = planes["Duration"].astype(float)

Böylece duration sütunu artık sayılardan oluşmaktadır. Bu sayılar ise dakikaya karşılık gelmektedir.

sns.histplot(data=planes, x="Duration")
plt.show()

En yaygın uçuş süresi yaklaşık 3 saat olarak gözükmektedir.

Şimdi ise havayolu şirketlerinin fiyatlarının standart sapmalarına bir bakalım.

planes["price_destination_mean"] = planes.groupby("Destination")["Price"].transform(lambda x: x.mean())

print(planes[["Destination","price_destination_mean"]].value_counts())
    Destination  price_destination_mean
Cochin 10506.993 4391
Banglore 9132.225 2773
Delhi 5157.794 1219
New Delhi 11738.589 888
Hyderabad 5025.210 673
Kolkata 4801.490 369
dtype: int64

Lambda fonksiyon türünü kullanarak değerleri ortalamaya dönüştürdük. Böylece varış bölgelerine göre gruplandırdık ve ortalama fiyat değerlerini elde ettik. Görünüşe göre ortalama olarak en pahalı uçuş bileti New Delhi hedefli uçuşlardadır.

Aykırı Değer

Aykırı değerler, bir veri setindeki sayısal değerler arasında en uçuk değerlerdir.

Örneğin bilet fiyatlarına bir bakalım.

sns.histplot(data=planes, x="Price")
plt.show()

Burada 40.000'den sonra iki adet veri noktası bulunmaktadır.

Uçuş sürelerine de bir göz atalım.

print(planes["Price"].describe())
    count    10044.000
mean 9044.411
std 4472.305
min 1759.000
25% 5276.750
50% 8366.000
75% 12373.000
max 54826.000
Name: Price, dtype: float64

Fiyat sütunu aykırı değer içermektedir. Çünkü veri setinin yoğunlaştığı, yani veri noktalarının birbirine yakınlaştığı yerlerden çok uzak veri noktaları bulunmaktadır.

Aykırı değerleri kaldırmak için yüzdelik dilimlerden faydalanabiliriz.

price_seventy_fifth = planes["Price"].quantile(0.75)
price_twenty_fifth = planes["Price"].quantile(0.25)

Burada 75. ve 25. yüzdelik dilimleri elde ettik.

prices_iqr = price_seventy_fifth - price_twenty_fifth

Burada ise 75. ve 25. yüzdelik dilimlerin farkını alarak çeyrekler açıklığını elde ettik.

upper = price_seventy_fifth + (1.5 * prices_iqr)
lower = price_twenty_fifth - (1.5 * prices_iqr)

Burada ise bu çeyrekler açıklığının, yani ortadaki %50'lik dilimin 1.5 katı altında ve yukarısında olan değerleri belirledik.

planes = planes[(planes["Price"] > lower) & (planes["Price"] < upper)]

Son olarak da bu aralığın içerisinde kalan değerleri seçtik.

print(planes["Price"].describe())
    count     9959.000
mean 8875.161
std 4057.202
min 1759.000
25% 5228.000
50% 8283.000
75% 12284.000
max 23001.000
Name: Price, dtype: float64

Gördüğünüz üzere aykırı değerleri çıkardığımızda istatistiksel değerler çok daha düzgün görünüyor. Orijinal veri setinde 55.000 fiyatlı bilet varken şimdi ise bu değer 23.000' düştü. Aykırı değerler ortalama gibi hesaplamalarda çok büyük oynaklığa sebep olurlar.

Son

Beni Linkedin ve Github üzerinden takip edebilirsiniz.

--

--