Pertemuan 1 : Python for Data Analysis

Intro to Pandas

Offline di Departemen Matematika: sesi 1 di lab komputer D.311, sesi 2 di lab statistika D.406
Published

February 27, 2024

Kembali ke EDA

Data Analysis Libraries

Library pada python adalah potongan kode yang reusable dan dapat kita akses dengan mengimpornya ke dalam program kita. Pada mata kuliah algoritma dan pemrograman yang telah kalian ambil di semester 1, telah diperkenalkan beberapa library yang dapat kalian import ke dalam script kalian seperti numpy, scipy, sympy, pandas, matplotlib dan lainnya.

Dalam bidang data analysis, library python yang umum digunakan adalah numpy dan pandas untuk pengolahan data tabular, matplotlib dan seaborn untuk visualisasi

Jika anda menggunakan jupyter notebook secara local pada perangkat anda, anda perlu menginstall 3 library tersebut untuk praktikum ini. Gunakan python package manager (pip) untuk menginstall library numpy, pandas, matplotlib dan seaborn dengan memanggil pip install <nama-library> di terminal. Jika anda menggunakan conda atau google colab, library-library ini sudah terinstall secara otomatis dan dapat kita import secara langsung.

Untuk mengecek apakah library yang diperlukan sudah terinstall, run blok kode di bawah ini.

# Untuk library lain, ubah `pandas` -> <nama-library>
import pandas
pandas.__version__

Jika library sudah terinstall, maka output akan menunjukkan versi dari library yang terinstall.

Kesepakatan Penamaan Library

Komunitas python memiliki kesepakatan penamaan untuk beberapa library untuk memudahkan pembacaan kode. Beberapa diantaranya yang kita gunakan adalah

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

Struktur Data Pandas (Series, DataFrame, Index Objects)

Pada dasarnya, pandas dibuat atas struktur data yang terdapat pada library NumPy yaitu Array. Array sendiri sudah pernah dibahas dalam mata kuliah algoritma dan pemrograman sehingga kita tidak akan mendalaminya pada praktikum ini.

Series

Series adalah suatu object yang menyerupai array 1 dimensi yang memiliki nilai dengan array index yang berkaitan dengan masing-masing nilai.

import pandas as pd

obj = pd.Series([4, 7, -5, 3])
obj
0    4
1    7
2   -5
3    3
dtype: int64

kolom kiri adalah index, kolom kanan adalah Values (nilai).


Untuk mengakses values saja :

obj.values
array([ 4,  7, -5,  3], dtype=int64)

Untuk mengakses index saja :

obj.index
RangeIndex(start=0, stop=4, step=1)

Perbedaan Series dengan Array

Dengan Series kita bisa menggunakan index untuk mengakses value yang berkaitan dengan index tersebut.

obj[0]
4
obj[1] = 2
obj[[0, 1, 3]]
0    4
1    2
3    3
dtype: int64
obj[0:2]
0    4
1    2
dtype: int64

Series dengan custom index

obj2 = pd.Series([0.25, 0.5, 0.75, 1.0], index=['d', 'b', 'a', 'c'])
obj2
d    0.25
b    0.50
a    0.75
c    1.00
dtype: float64
Error warning

Perhatikan jumlah index harus sama dengan jumlah value yang ditetapkan.

obj2.index
Index(['d', 'b', 'a', 'c'], dtype='object')
obj2['b']
0.5
obj2['d':'a']
d    0.25
b    0.50
a    0.75
dtype: float64

Untuk mengubah index suatu series bisa juga dengan mengubah nilai <series>.index

obj2.index = ['A', 'B', 'C', 'D']
obj2
A    0.25
B    0.50
C    0.75
D    1.00
dtype: float64

Series as specialized dictionary

Dictionary pada python adalah struktur data yang berisi pasangan key-value. Kita dapat melihat series sebagai pasangan key-value dengan index sebagai key. Bahkan kita bisa membuat suatu series dari sebuah dictionary.

data_dict = {
  'Jakarta': 400,
  'Bandung': 200,
  'Bogor': 300,
  'Depok': 500
}
data_dict
{'Jakarta': 400, 'Bandung': 200, 'Bogor': 300, 'Depok': 500}
data_series = pd.Series(data_dict)
data_series
Jakarta    400
Bandung    200
Bogor      300
Depok      500
dtype: int64

Jika kita ingin index dengan urutan tertentu, maka kita dapat memasukkan argumen index berupa list index sesuai dengan urutan yang kita inginkan.

kota = ['Surabaya', 'Bandung', 'Bogor', 'Jakarta']
data_series2 = pd.Series(data_dict, index=kota)
data_series2
Surabaya      NaN
Bandung     200.0
Bogor       300.0
Jakarta     400.0
dtype: float64
Tip

Perhatikan bahwa jika kita memasukkan index yang tidak ada pada dictionary awal, index akan dimasukkan dengan nilai NaN (Not a Number)


Operasi Aritmatika

Series secara otomatis menyamakan index ketika melakukan operasi aritmatika.

data_series + data_series2 # Silahkan coba untuk operasi aritmatika lainnya
Bandung     400.0
Bogor       600.0
Depok         NaN
Jakarta     800.0
Surabaya      NaN
dtype: float64
Tip

Perhatikan bahwa Depok dan Surabaya bernilai NaN. Hal ini dikarenakan kedua index tersebut tidak terdapat pada kedua series yang kita operasikan.


name attribute

Object series dan index pada pandas memiliki atribut name yaitu nama dari series/index tersebut.

data_series.name = 'populasi'
data_series.index.name = 'kota'
data_series
kota
Jakarta    400
Bandung    200
Bogor      300
Depok      500
Name: populasi, dtype: int64

DataFrame

DataFrame adalah struktur data 2 dimensi yang terdiri atas baris dan kolom (disebut juga tabel). Kita dapat melihat dataframe sebagai gabungan dari 2 atau lebih series.

Karena memiliki 2 dimensi (baris dan kolom), DataFrame memiliki indeks untuk masing-masing baris dan kolom.


Ada banyak cara untuk membangun DataFrame, salah satu yang paling umum adalah membuat dictionary dengan

key : nama kolom
value : nilai-nilai dalam list atau NumPy Array dengan panjang yang sama untuk setiap kolom.
data = {'kota': ['Bogor', 'Bogor', 'Bogor', 'Depok', 'Depok', 'Depok'],
 'tahun': [2000, 2001, 2002, 2001, 2002, 2003],
 'populasi': [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]}

df = pd.DataFrame(data) # `df` adalah singkatan yang umum digunakan oleh komunitas python dalam mendefinisikan suatu `dataframe`

df
kota tahun populasi
0 Bogor 2000 1.5
1 Bogor 2001 1.7
2 Bogor 2002 3.6
3 Depok 2001 2.4
4 Depok 2002 2.9
5 Depok 2003 3.2

pd.DataFrame() menerima argumen columns= yang dapat digunakan untuk menentukan urutan kolom dataframe.

df2 = pd.DataFrame(data, columns=['tahun', 'kota', 'populasi'])

df2
tahun kota populasi
0 2000 Bogor 1.5
1 2001 Bogor 1.7
2 2002 Bogor 3.6
3 2001 Depok 2.4
4 2002 Depok 2.9
5 2003 Depok 3.2
Tip
  • menambahkan kolom baru yang tidak ada pada data akan menghasilkan kolom berisi nilai NaN
  • pd.DataFrame juga menerima argumen index= untuk mengubah index seperti pada pd.Series
df2 = pd.DataFrame(data, columns=['tahun', 'kota', 'populasi', 'luas_wilayah'], 
                   index=['one', 'two', 'three', 'four', 'five', 'six'])

df2
tahun kota populasi luas_wilayah
one 2000 Bogor 1.5 NaN
two 2001 Bogor 1.7 NaN
three 2002 Bogor 3.6 NaN
four 2001 Depok 2.4 NaN
five 2002 Depok 2.9 NaN
six 2003 Depok 3.2 NaN

Importing datasets

Dalam mengolah suatu data, tidaklah mungkin kita harus menulis ulang seluruh data yang sudah tertulis dengan format tertentu (misalnya Spreadsheet/.xlsx, .csv, atau .dat) pastinya kita perlu suatu cara untuk mengimpor data yang memiliki berbagai format. Pandas memiliki beberapa function yang dapat kita gunakan untuk membaca data dengan berbagai format.

  • .csv (comma separated values)
df = pd.read_csv('<path-to-csv>')
  • .xlsx (excel spreadsheet)
df = pd.read_excel('<path-to-xlsx>')
  • Others

Untuk tipe file lainnya, silahkan baca dokumentasi pandas di link berikut : Pandas IO Tools

DataFrame Attributes/Properties and Methods

Sejauh ini kita sudah berkenalan dengan 2 object pandas yaitu Series dan DataFrame. Dalam pemrograman python, sebuah object bisa memiliki suatu method, attribute/property, atau keduanya.

Untuk materi selanjutnya, kita akan menggunakan dataset pokemon sebagai contoh. Jalankan code block di bawah ini.

df = pd.read_csv('https://raw.githubusercontent.com/farhanage/dataset-for-study/main/pokemon_data.csv')

tail()

Memanggil method tail akan mengembalikan beberapa baris terakhir dari suatu dataframe.

df.tail(3)  # Membaca 3 baris terakhir
# Name Type 1 Type 2 HP Attack Defense Sp. Atk Sp. Def Speed Generation Legendary
797 720 HoopaHoopa Confined Psychic Ghost 80 110 60 150 130 70 6 True
798 720 HoopaHoopa Unbound Psychic Dark 80 160 60 170 130 80 6 True
799 721 Volcanion Fire Water 80 110 120 130 90 70 6 True
Tip

Secara default, method head() dan tail() akan mengembalikan 5 baris pertama/terakhir jika tidak diberikan suatu argumen.


shape

Memanggil attribute shape akan memberikan kita jumlah baris dan kolom dari suatu dataframe.

df.shape  # Mengembalikan (jumlah_baris, jumlah_kolom)
(800, 12)

columns

Memanggil attribute columns akan memberikan kita index object berisi semua nama kolom dari suatu dataframe.

df.columns  # Mengembalikan index object berisi semua nama kolom dari suatu dataframe
Index(['#', 'Name', 'Type 1', 'Type 2', 'HP', 'Attack', 'Defense', 'Sp. Atk',
       'Sp. Def', 'Speed', 'Generation', 'Legendary'],
      dtype='object')

index

Memanggil attribute columns akan memberikan kita index object berisi index baris suatu dataframe.

df.index  # Mengembalikan index object berisi index suatu dataframe
RangeIndex(start=0, stop=800, step=1)

Pandas dataframe memiliki banyak sekali methods dan attributes/properties. Untuk mempelajari lebih lanjut mengenai dataframe pandas, dokumentasi library pandas bisa diakses pada link berikut : Pandas essential basic functionality

Index

Perhatikan pada atribut columns dan index yang telah dibahas sebelumnya, output kode adalah object index. Apa itu object index? Dalam library Pandas, object index digunakan sebagai object yang menyimpan label suatu object lainnya.

Contoh : dalam object DataFrame, index object digunakan untuk menyimpan label baris (df.index) dan kolom (df.columns).

obj = pd.Series(range(3), index=['a', 'b', 'c'])
obj.index
Index(['a', 'b', 'c'], dtype='object')
obj.index[1]
'b'
obj.index[1:]
Index(['b', 'c'], dtype='object')
Error warning

Index object bersifat immutable, artinya nilai dari suatu index tidak dapat diubah.

obj.index[1] = 'd'
TypeError: Index does not support mutable operations

Index object juga memiliki beberapa attribute dan methods. Beberapa diantaranya :

Basic Functionality

Indexing, Selection and Filtering

Indexing and Selection

Series indexing digunakan untuk mengambil value yang berkaitan dengan suatu index.

import numpy as np
obj = pd.Series(np.arange(4.), index=['a', 'b', 'c', 'd'])
obj
a    0.0
b    1.0
c    2.0
d    3.0
dtype: float64
obj['b']  # Memanggil nilai dengan index `a`
1.0
obj['a':'c']  # Memanggil nilai dengan index `a` hingga `c`
a    0.0
b    1.0
c    2.0
dtype: float64
Important

Perhatikan, saat melakukan slicing dengan explicit index (misal, data['a':'c']), final index diikutsertakan dalam outputnya (inklusif), sementara ketika melakukan slicing dengan implicit index (misal, data[0:2]), final index tidak diikutsertakan dalam outputnya (tidak inklusif).


DataFrame indexing digunakan untuk mengambil 1 atau beberapa kolom dengan memanggil label/nama kolom yang bersesuaian.

df['Name']
0                  Bulbasaur
1                    Ivysaur
2                   Venusaur
3      VenusaurMega Venusaur
4                 Charmander
               ...          
795                  Diancie
796      DiancieMega Diancie
797      HoopaHoopa Confined
798       HoopaHoopa Unbound
799                Volcanion
Name: Name, Length: 800, dtype: object
df[['Name']]
Name
0 Bulbasaur
1 Ivysaur
2 Venusaur
3 VenusaurMega Venusaur
4 Charmander
... ...
795 Diancie
796 DiancieMega Diancie
797 HoopaHoopa Confined
798 HoopaHoopa Unbound
799 Volcanion

800 rows × 1 columns

Important
df['<column-name>'] akan mengembalikan kolom yang bersesuaian dalam bentuk Series.
df[['<column-name>']] akan mengembalikan kolom yang bersesuaian dalam bentuk dataframe.
df[['Name','HP','Defense']]
Name HP Defense
0 Bulbasaur 45 49
1 Ivysaur 60 63
2 Venusaur 80 83
3 VenusaurMega Venusaur 80 123
4 Charmander 39 43
... ... ... ...
795 Diancie 50 150
796 DiancieMega Diancie 50 110
797 HoopaHoopa Confined 80 60
798 HoopaHoopa Unbound 80 60
799 Volcanion 80 120

800 rows × 3 columns


Kita bisa membuat suatu kolom baru dari kolom-kolom yang sudah ada. Misalkan kita buat suatu variabel bernama Total Attack yang berisi hasil penjumlahan variabel Attack dan Sp. Atk

df['Total Attack'] = df['Attack'] + df['Sp. Atk']
df[['Attack','Sp. Atk','Total Attack']]
Attack Sp. Atk Total Attack
0 49 65 114
1 62 80 142
2 82 100 182
3 100 122 222
4 52 60 112
... ... ... ...
795 100 100 200
796 160 160 320
797 110 150 260
798 160 170 330
799 110 130 240

800 rows × 3 columns


Untuk indexing baris suatu dataframe, gunakan index baris

df[:3] # Mengambil baris dengan index 0 - 2
# Name Type 1 Type 2 HP Attack Defense Sp. Atk Sp. Def Speed Generation Legendary Total Attack
0 1 Bulbasaur Grass Poison 45 49 49 65 65 45 1 False 114
1 2 Ivysaur Grass Poison 60 62 63 80 80 60 1 False 142
2 3 Venusaur Grass Poison 80 82 83 100 100 80 1 False 182
df[0:5:2]  # Mengambil dengan index 0 sampai 4 dengan step 2
# Name Type 1 Type 2 HP Attack Defense Sp. Atk Sp. Def Speed Generation Legendary Total Attack
0 1 Bulbasaur Grass Poison 45 49 49 65 65 45 1 False 114
2 3 Venusaur Grass Poison 80 82 83 100 100 80 1 False 182
4 4 Charmander Fire NaN 39 52 43 60 50 65 1 False 112

Filtering

Untuk melakukan filtering pada suatu dataframe :

df[df['HP'] == 50]  # Mengambil data pada dataframe df yang memiliki nilai kolom `HP` == 50
# Name Type 1 Type 2 HP Attack Defense Sp. Atk Sp. Def Speed Generation Legendary Total Attack
14 11 Metapod Bug NaN 50 20 55 25 25 30 1 False 45
32 27 Sandshrew Ground NaN 50 75 85 20 30 40 1 False 95
59 54 Psyduck Water NaN 50 52 48 65 50 55 1 False 117
75 69 Bellsprout Grass Poison 50 75 35 70 30 40 1 False 145
83 77 Ponyta Fire NaN 50 85 55 65 65 90 1 False 150
... ... ... ... ... ... ... ... ... ... ... ... ... ...
760 690 Skrelp Poison Water 50 60 60 60 60 30 6 False 120
762 692 Clauncher Water NaN 50 53 62 58 63 44 6 False 111
773 703 Carbink Rock Fairy 50 50 150 50 150 50 6 False 100
795 719 Diancie Rock Fairy 50 100 150 100 150 50 6 True 200
796 719 DiancieMega Diancie Rock Fairy 50 160 110 160 110 110 6 True 320

63 rows × 13 columns

df[df['HP'] > 50]  # Mengambil data pada dataframe df yang memiliki nilai kolom `HP` > 50
# Name Type 1 Type 2 HP Attack Defense Sp. Atk Sp. Def Speed Generation Legendary Total Attack
1 2 Ivysaur Grass Poison 60 62 63 80 80 60 1 False 142
2 3 Venusaur Grass Poison 80 82 83 100 100 80 1 False 182
3 3 VenusaurMega Venusaur Grass Poison 80 100 123 122 120 80 1 False 222
5 5 Charmeleon Fire NaN 58 64 58 80 65 80 1 False 144
6 6 Charizard Fire Flying 78 84 78 109 85 100 1 False 193
... ... ... ... ... ... ... ... ... ... ... ... ... ...
793 717 Yveltal Dark Flying 126 131 95 131 98 99 6 True 262
794 718 Zygarde50% Forme Dragon Ground 108 100 121 81 95 95 6 True 181
797 720 HoopaHoopa Confined Psychic Ghost 80 110 60 150 130 70 6 True 260
798 720 HoopaHoopa Unbound Psychic Dark 80 160 60 170 130 80 6 True 330
799 721 Volcanion Fire Water 80 110 120 130 90 70 6 True 240

589 rows × 13 columns

df[(df['HP'] > 100) & (df['Type 1'] == 'Fire')]  # Mengambil data pada dataframe df yang memiliki nilai kolom `HP` > 100 dan `Type 1` == Fire
# Name Type 1 Type 2 HP Attack Defense Sp. Atk Sp. Def Speed Generation Legendary Total Attack
263 244 Entei Fire NaN 115 115 85 90 75 100 2 True 205
270 250 Ho-oh Fire Flying 106 130 90 110 154 90 2 True 240
559 500 Emboar Fire Fighting 110 123 65 100 65 65 5 False 223
615 555 DarmanitanStandard Mode Fire NaN 105 140 55 30 55 95 5 False 170
616 555 DarmanitanZen Mode Fire Psychic 105 30 105 140 105 55 5 False 170
df[(df['HP'] > 100) | (df['Type 1'] == 'Fire')]  # Mengambil data pada dataframe df yang memiliki nilai kolom `HP` > 100 atau `Type 1` == Fire
# Name Type 1 Type 2 HP Attack Defense Sp. Atk Sp. Def Speed Generation Legendary Total Attack
4 4 Charmander Fire NaN 39 52 43 60 50 65 1 False 112
5 5 Charmeleon Fire NaN 58 64 58 80 65 80 1 False 144
6 6 Charizard Fire Flying 78 84 78 109 85 100 1 False 193
7 6 CharizardMega Charizard X Fire Dragon 78 130 111 130 85 100 1 False 260
8 6 CharizardMega Charizard Y Fire Flying 78 104 78 159 115 100 1 False 263
... ... ... ... ... ... ... ... ... ... ... ... ... ...
769 699 Aurorus Rock Ice 123 77 72 99 92 58 6 False 176
792 716 Xerneas Fairy NaN 126 131 95 131 98 99 6 True 262
793 717 Yveltal Dark Flying 126 131 95 131 98 99 6 True 262
794 718 Zygarde50% Forme Dragon Ground 108 100 121 81 95 95 6 True 181
799 721 Volcanion Fire Water 80 110 120 130 90 70 6 True 240

114 rows × 13 columns

Untuk filter yang lebih rumit, disarankan untuk mendefinisikan variabel condition agar kode mudah terbaca.

Contoh : Filter (HP >= 150) dan ((Type 1 == Water) atau (Legendary == True))

condition = (df['HP'] >= 150) & ((df['Type 1'] == 'Water') | (df['Legendary'] == True))
df[condition]
# Name Type 1 Type 2 HP Attack Defense Sp. Atk Sp. Def Speed Generation Legendary Total Attack
351 321 Wailord Water NaN 170 90 45 90 45 60 3 False 180
544 487 GiratinaAltered Forme Ghost Dragon 150 100 120 100 120 90 4 True 200
545 487 GiratinaOrigin Forme Ghost Dragon 150 120 100 120 100 90 4 True 240
655 594 Alomomola Water NaN 165 75 80 40 45 65 5 False 115

Case Study (Toko Baju Unikloh)

Link Dataset : Data Penjualan Toko Baju Unikloh

Sebuah toko baju unikloh membutuhkan jasa seorang analis untuk menganalisis data penjualan baju yang mereka miliki. Sebelum itu, Pak Joko selaku pemilik toko ingin tahu beberapa hal mengenai data yang dia miliki. Berikut yang beliau minta :

  1. Ada berapa banyak data penjualan kita?
Hint
# Jumlah data penjualan bisa kita akses dengan melihat `jumlah baris` suatu dataframe.
Answer
sales_df.shape
  1. Beliau minta 10 data pertama untuk melihat gambaran umum nilai masing-masing variabel.
Hint
# Gunakan method `.head()`
Answer
sales_df.head(10)
  1. Beliau mau fokus melihat kolom price_per_unit dan quantity saja.
Hint
# Untuk mengambil beberapa kolom dari suatu dataframe, gunakan `df[[<nama-kolom-1>, <nama-kolom-2>, <nama-kolom-3>, ...]]`
Answer
sales_df[['price_per_unit','quantity']]
  1. Harusnya di data ini ada kolom total harga pembelian yang dinamakan total_price, tapi sepertinya kolomnya hilang. Tolong buatkan kolomnya berdasarkan data yang ada.
Hint 1
# Perhatikan bahwa total harga pembelian = kuantitas x harga per unit
Hint 2
# Buat kolom baru dari hasil kali 2 kolom tersebut dengan df[...] = df[...]*df[...].
Answer
sales_df['total_price'] = sales_df['price_per_unit']*sales_df['quantity']
  1. Beliau mau tau ada berapa banyak penjualan yang total harganya lebih besar dari $200
Hint
# Untuk memfilter suatu dataset, gunakan df[kondisi]
Answer
sales_df[sales_df['total_price'] > 200]
  1. Beliau mau tau ada berapa banyak penjualan yang total harganya berada di kisaran $200-250
Hint
# Gunakan `&` untuk filter dengan 2 kondisi yang dihubungkan operator `dan`.
Answer
sales_df[(sales_df['total_price'] > 200) & (sales_df['total_price'] < 200)]
  1. Terakhir, Beliau mau tau ada berapa banyak penjualan yang total harganya di kisaran $200-250 + penjualan 3 barang dalam satu pesanan.
Hint
# Gunakan `|` untuk filter dengan 2 kondisi yang dihubungkan operator `atau`.
Answer
condition = (((sales_df['total_price'] > 200) & (sales_df['total_price'] < 200)) | (sales_df['quantity'] == 3))
sales_df[condition]