Advanced Materials of Python

Table of Contents

Hits

1. Function II

1.1. Function的特性

  1. Fnction為物件
    1: def yell(text):
    2:     return text.upper() + '!'
    3: 
    4: print(yell('hello'))
    5: bark = yell # assign給其他變數
    6: print(bark('woof'))
    
    HELLO!
    WOOF!
    
  2. Function可被放在資料結構中
    1: def yell(text):
    2:     return text.upper() + '!'
    3: 
    4: bark = yell
    5: 
    6: funcs = [bark, str.lower, str.capitalize]
    7: 
    8: for func in funcs:
    9:     print(func('hey there!'))
    
    HEY THERE!!
    hey there!
    Hey there!
    
  3. Function可做為參數傳給其他function

    Function是物件,所以除了能指定給變數,更可以當成其他function的參數。

     1: def yell(text):
     2:     return text.upper() + '!'
     3: 
     4: bark = yell
     5: 
     6: def greet(func):
     7:     greeting = func('Hi, I am a Python Program')
     8:     print(greeting)
     9: 
    10: greet(bark)
    
    HI, I AM A PYTHON PROGRAM!
    

    這種能接鶢其他function做為參數的格式也稱為高階function(higher-order function)。Python內建的map function即為higher-order的經典範例,它接受一個function object與一個可走訪物件(iterable)做為參數,然後會套用該function到iterable object內的每一元素,產出一系列結果:

    1: list = map(function, iterable object)
    

    例如:

    1: def yellp(text):
    2:     return text.upper() + '!'
    3: 
    4: bark = yell
    5: 
    6: list1 = ['hello', 'hey', 'hi']
    7: print(list(map(bark, list1)))
    
  4. Function可構成巢狀結構

    在function內定義function,稱之為巢狀function(nested function)或內部function(inner function)

    1: def speak(text):
    2:     def whisper(t):
    3:         return t.lower() + '...'
    4:     return whisper(text)
    5: 
    6: print(speak('Hello, world'))
    
    hello, world...
    

    每次呼叫speak時,它會先定義內部function,而這個inner function在speak外並不存在。若一定要在speak之外存取內部function whisper,則要先把內部function傳回給父function的呼叫者:

     1: def get_speak_func(volume):
     2:     def whisper(text):
     3:         return text.lower() + '...'
     4:     def yell(text):
     5:         return text.upper() + '!'
     6:     if volume > 0.5:
     7:         return yell
     8:     else:
     9:         return whisper
    10: 
    11: print(get_speak_func(0.3))
    12: print(get_speak_func(0.7))
    
    <function get_speak_func.<locals>.whisper at 0x101986af0>
    <function get_speak_func.<locals>.yell at 0x101986a60>
    
  5. Inner function可記住父function的參數狀態

    Function還可「捕捉並保留父function的部份狀態」:

     1: def get_speak_func(text, volume):
     2:     def whisper():
     3:         return text.lower() + '...'
     4:     def yell():
     5:         return text.upper() + '!'
     6:     if volume > 0.5:
     7:         return yell
     8:     else:
     9:         return whisper
    10: 
    11: func = get_speak_func('Hello, world', 0.7)
    12: print(func())
    
    HELLO, WORLD!
    

    這種會捕捉並記住外部參數的function,稱之為詞法閉包(lexical closuer),或簡稱閉包(closure)。閉包會記住外圍程式範圍裡的變數值,即便其程式流程已經離開該變數所在的範圍也一樣。

  6. 物件也能像function一樣被呼叫

    在Python中,function皆為object,反之object不見得是function,不過,我們能讓不是function的object變成可呼叫(callable),許多情況下,甚至可以把callable object當成function,在後面加上小括號來呼叫它,甚至能傳入參數。要使object變為callable,方法是在類別加入__call__:

    1: class Addr:
    2:     def __init__(self, n):
    3:         self.n = n
    4:     def __call__(self, x):
    5:         return self.n + x
    6: 
    7: plus_3 = Addr(3) #建立Addr物件,屬性n=3
    8: print(plus_3(4))
    
    7
    

1.2. lambda

lambda提供了可用來宣告小型匿㢱function的快速宣告方式:

函式名稱 = lambda 參數:運算式

這種語法也稱為function expression,這與用def宣告的function一樣

1: add = lambda x, y : x + y
2: 
3: print(add(5, 3))
8

那麼,以lambda定義function的優勢為何?

1: print((lambda x, y : x + y)(5, 3))
8

在許多場合中,使用lambda來定義臨時性的function就比較方便,但labmda function有個語法上的限制:只能含有一條運算式。

lambda function使用時機: 任何需要快速用上function object的地方,例如sorted():

1: sorted(iterable object, key=function)

其中參數key可輸入一個function, sorted()會依據function的傳回值來排序物件的元素,如果忽略key參數,則會依據原始元素(或者元素的第一個值)來排序。

1: tuples_list = [(1, 'd'), (2, 'b'), (4, 'a'), (3, 'c')]
2: print(sorted(tuples_list))
3: 
4: print(sorted(tuples_list, key=lambda x: x[1])) #指定key以tuple元素的第二子元素來排序
5: print(sorted(range(-5, 6), key=lambda x: x**2)) #指定依平方值來排序
6: # 上述方式只是為展示lambda用法,更精簡的方式為:
7: print(sorted(range(-5, 6), key=abs))
[(1, 'd'), (2, 'b'), (3, 'c'), (4, 'a')]
[(4, 'a'), (2, 'b'), (3, 'c'), (1, 'd')]
[0, -1, 1, -2, 2, -3, 3, -4, 4, -5, 5]
[0, -1, 1, -2, 2, -3, 3, -4, 4, -5, 5]

使用lambda的缺點:不易閱讀

1.3. decorator

Python的修飾器(decorator)可用來間接修改callable object(function, method, class)的行為,但不直接改動object本身,其用途包括:

  • 加入日誌(logging)
  • 存取權限管控與身份驗證
  • 加入監測function及衡量執行時間
  • 限制function執行頻率
  • 用快取暫存function的執行結果
  1. 修飾無參數function
     1: def uppercase(func):
     2:     def wrapper():
     3:         original_result = func()
     4:         modified_result = original_result.upper()
     5:         return modified_result
     6:     return wrapper
     7: 
     8: @uppercase #以uppercase修飾greet
     9: def greet():
    10:     return 'Hello'
    11: 
    12: print(greet())
    
    HELLO
    
  2. 多重修飾
     1: def strong(func):
     2:     def wrapper():
     3:         return f'<strong> { func() } </strong>'
     4:     return wrapper
     5: 
     6: def emphasis(func):
     7:     def wrapper():
     8:         return f'<em> { func() } </em>'
     9:     return wrapper
    10: 
    11: @strong
    12: @emphasis
    13: def greet():
    14:     return 'Hello!'
    15: 
    16: print(greet)
    17: print(greet())
    
    <function strong.<locals>.wrapper at 0x10fd3ab80>
    <strong> <em> Hello! </em> </strong>
    

    唯一要注意的是:若套用修飾器的層級過多會影響程式執行效能。

  3. 修飾有傳入參數的function

    若要修飾的function有帶參數,則wrapper()要改為wrapper(*args, **kwargs),這分別用來收集所有傳入的positional與keyword類參數,以tuple及dict形式儲存在arfs與kwargs變數裡,然後wrapper利用*與**將收集到的參數unpack後傳給原始func,如此就不會限制到參數個數。

     1: def trace(func):
     2:     def wrapper(*args, **kwargs):
     3:         print(f'trace: callable function {func.__name__}, with arguments: {args}, {kwargs}')
     4:         original_results = func(*args, **kwargs)
     5:         print(f'trace: function {func.__name__} reutrn results: {original_results}!')
     6:         return original_results
     7:     return wrapper
     8: 
     9: @trace
    10: def say(name, line):
    11:     return f'{name}, {line}'
    12: 
    13: print(say('James', 'hi'))
    
    trace: callable function say, with arguments: ('James', 'hi'), {}
    trace: function say reutrn results: James, hi!
    James, hi
    

    幫function加上效能監測功能

     1: import time
     2: def eval_time(func):
     3:     def wrapper(*args, **kwargs):
     4:         start = time.perf_counter()
     5:         res = func(*args, **kwargs)
     6:         finish = time.perf_counter()
     7:         print(f'Finished in {round(finish-start, 2)} second(s)')
     8:         return res
     9:     return wrapper
    10: 
    11: @eval_time
    12: def sigma(n):
    13:     sum = 0
    14:     for i in range(n):
    15:         sum = sum + i
    16:     return sum
    17: 
    18: print(sigma(100000000))
    
    Finished in 5.3 second(s)
    4999999950000000
    

1.4. *args and **kwargs

*與**能讓function接受數量不定的額外參數,讓module與class能提供更有彈性的存取介面:

 1: def foo(required, *args, **kwargs):
 2:     print(required)
 3:     if args:
 4:         print(f'args: {args}')
 5:     if kwargs:
 6:         print(f'kwargs: {kwargs}')
 7: 
 8: foo('1. Hi')
 9: foo('2. Hi', 1, 2, 3)
10: foo('3. Hi', 1, 2, 3, Key1='value1', key2=456)
1. Hi
2. Hi
args: (1, 2, 3)
3. Hi
args: (1, 2, 3)
kwargs: {'Key1': 'value1', 'key2': 456}
  • *args接收額外的位置型參數(positional paramenters)(沒有鍵的參數),將之放入一個tuple。
  • **kwargs接收額外的關鍵字參數(keyword parameters)(有鍵的參數),將之放入一個dict。

1.5. Unpack function parameters

*與**也可用來unpack function的args與kwargs,在呼叫function時,若在iterable object前加上*,就會unpack這個物件,將元素當成個別的positional parameters傳入參數;若加上**,則可unpack keyword parameters。

1: def print_vector(x, y, z):
2:     print(f'<{x}, {y}, {z}>')
3: 
4: tuple_vector = (1, 0, 1)
5: print_vector(tuple_vector[0], tuple_vector[1], tuple_vector[2])
6: print_vector(*tuple_vector)
7: 
8: dict_vector = { 'y': 4, 'z': 5, 'x': 3}
9: print_vector(**dict_vector)
<1, 0, 1>
<1, 0, 1>
<3, 4, 5>

1.6. Return or not

  • return 與return None效果一樣
  • 如果沒有傳回值,則可省略return,例如,一個只負責print的function並無任值可傳回,寫return會很奇怪。

2. Class v.s. Object

2.1. TODO 什麼是class? 什麼是object?

把這個看完: https://youtu.be/JeznW_7DlB0:

Everything in Python is object.

1: a = 2022
2: b = 'TNFSH'
3: 
4: def c():
5:     print('TNFSH')
6: 
7: print(type(a))
8: print(type(b))
9: print(type(c))
<class 'int'>
<class 'str'>
<class 'function'>

由上面的例子,我們可以發現:在Python裡,幾乎所有我們用到的變數、函數都是class。
當我們寫下

1: a = 2022

其實是在說,我們有一個變數,它是一種int class,它的值是2022。這個變數就是一個object: 某種屬於某一類class的實體。
一種常見的說法是:class是一張藍圖,依照這張藍圖蓋出的房子有同樣的架構,有同樣的功能,但是可能在外觀或顏色上會有些許差異,就好比一樣是整數物件,每個變數的值可能會有所不同。

不同的class有其特定的功能與限制,例如:

1: x = 1
2: y = 'TNFSH'
3: print(x+y)

執行上述程式會得到以下錯誤訊息:

  File "<stdin>", line 3, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'

這裡告訴我們:int與str這兩種class的object不支援+這個運算。我們再來看另一個例子:

1: x = 'tnfsh'
2: y = 1
3: print(x.upper())
4: print(y.upper())
TNFSH

上述程式會產生如下錯誤訊息:
#+begin_src shell -r -n :results output :exports both
Traceback (most recent call last):
File “<stdin>”, line 4, in <module>
AttributeError: ’int’ object has no attribute ’upper’
#+end_src<<sh
意思是:int這種class的object沒有一種叫做upper的屬性。這就是我們上面所說的,不同的class有其特定的功能與限制。

2.2. 建立自己的class

1: class Dog:
2:     def bark(self):
3:         print('汪汪汪')
4: 
5: a = Dog()
6: print(type(a))
7: a.bark()
<class '__main__.Dog'>
汪汪汪

在上述範例中,我們建立了一個叫Dog的class,然後再建立一個屬於Dog這個class的object。
從這個object的type,可以看出這是一個叫<class ’main.Dog’>的類別,這裡的__main__意思是這個class被定義(或撰寫)在目前這支Python程式中,當然我們也可以另外開一個新的.py檔專問來儲存這個class。

2.3. == v.s. is

  • ==: equal
  • is: identical
1: a = [1, 2, 3]
2: b = [1, 2, 3]
3: c = a
4: print(a == b)
5: print(a is b)
6: print(a == c)
7: print(a is c)
True
False
True
True

2.4. Shallow copy v.s. Deep copy

簡單來說,淺與深的區別1

  • 淺複製僅複製容器中元素的地址
  • 深複製完全複製了一份副本,容器與容器中的元素地址都不一樣
  1. shallow copy
     1: xs = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
     2: ys = list(xs) # shallow copy
     3: print(xs == ys) # 二者內容相同
     4: print(xs is ys) # 但為不同物件
     5: 
     6: xs.append([10, 11, 12])
     7: print(xs)
     8: print(ys)
     9: 
    10: # 但若是修改二者相同的部份,如
    11: xs[1][0] = 'X'
    12: print(xs)
    13: print(ys)
    
    True
    False
    [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]
    [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    [[1, 2, 3], ['X', 5, 6], [7, 8, 9], [10, 11, 12]]
    [[1, 2, 3], ['X', 5, 6], [7, 8, 9]]
    

    由上述例子中可發現,xs與ys其實共享同一份資料。

  2. deep copy
     1: import copy
     2: xs = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
     3: zs = copy.deepcopy(xs)
     4: 
     5: print(xs == zs)
     6: print(xs is zs)
     7: 
     8: xs[1][0] = 'X'
     9: print(xs)
    10: print(zs)
    
    True
    False
    [[1, 2, 3], ['X', 5, 6], [7, 8, 9]]
    [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    
  3. copy any object

    不管想copy什麼object,包括自訂物件,解決之道亦是用copy module的copy()與deepcopy()。

     1: import copy
     2: 
     3: class Point:
     4:     def __init__(self, x, y):
     5:         self.x = x
     6:         self.y = y
     7:     def __repr__(self):
     8:         return f'Point({self.x}, {self.y})'
     9: 
    10: a = Point(23, 42)
    11: b = Point(23, 42)
    12: #b = copy.copy(a)
    13: print(a)
    14: print(b)
    15: print(a == b)
    16: print(a is b)
    
    Point(23, 42)
    Point(23, 42)
    False
    False
    

2.5. ABC: Abstract Base Class

C++、Java等語言都有「介面」(interface)這種本身無法建立object、卻能當成建立object template的機制。Python則可藉由ABC來實作。藉由ABC來建立class,便是可確保繼承之sub-class均有正確的特定method。

 1: from abc import ABC, abstractmethod
 2: 
 3: class Base(ABC):
 4: 
 5:     @abstractmethod
 6:     def foo(self):
 7:         pass
 8: 
 9:     @abstractmethod
10:     def bar(self):
11:         pass
12: 
13: class Concrete(Base):
14:     def foo(self):
15:         pass
16: 
17: print(issubclass(Concrete, Base))
True

2.6. object/class/static method

 1: class MyClass:
 2: 
 3:     def objmethod(self): #object method
 4:         print(f'呼叫object method: {self}')
 5: 
 6:     @classmethod
 7:     def clsmethod(cls):
 8:         print(f'呼叫class method: {cls}')
 9: 
10:     @staticmethod
11:     def stcmethod():
12:         print(f'呼叫static method')
13: 
14: obj = MyClass()
15: obj.objmethod()
16: obj.clsmethod()
17: obj.stcmethod()
呼叫object method: <__main__.MyClass object at 0x1081f7940>
呼叫class method: <class '__main__.MyClass'>
呼叫static method
  • objmethod為一般object method,必須有self參數,該參數指向object本身
  • csmethod為class method,必項有cls參數,指向class本身
  • stcmethod為static method,沒有參數,無法修改物件或類別狀態,只能處理user傳入的參數
  1. DEMO
     1: class Drink:
     2:     def __init__(self, name, price, counts):
     3:         self.name = name
     4:         self.price = price
     5:         self.counts = counts
     6: 
     7:     def costs(self):
     8:         return self.drink_costs(self.price, self.counts)
     9: 
    10:     @classmethod
    11:     def blacktea(cls):
    12:         return cls('紅茶', 1, 25)
    13: 
    14:     @classmethod
    15:     def coffee(cls):
    16:         return cls('熱美式', 1, 50)
    17: 
    18:     @staticmethod
    19:     def drink_costs(price, counts):
    20:         return price * counts
    21: 
    22: drink1 = Drink.blacktea()
    23: drink2 = Drink.coffee()
    24: drink3 = Drink('義式', 60, 3)
    25: print(f'{drink1.name}')
    26: print(f'{drink2.price}')
    27: print(f'{drink3.name!r} * {drink3.counts} = {drink3.costs()}')
    
    紅茶
    1
    '義式' * 3 = 180
    

2.7. class variable v.s. instance variable

  • class variable: 宣告在classs定義裡,但宣告位置在所有method之外
  • instance variable: 屬於由class所建立的某個instance,其內容並非存在class中,而是由各別instance負責
  1. 差異
     1: class Dog:
     2:     num_dogs = 0 #Demo class variable的適用時機
     3:     num_leg = 4
     4: 
     5:     def __init__(self, name):
     6:         self.name = name
     7:         self.__class__.num_dogs += 1
     8: 
     9: dd = Dog('DaiDai')
    10: bd = Dog('Bad luck')
    11: lk = Dog('Lucky')
    12: bd.num_leg = 3
    13: print(dd.num_leg)
    14: print(bd.num_leg)
    15: print(f'目前共有{Dog.num_dogs}隻狗')
    
    4
    3
    目前共有3隻狗
    

    由class variable num_dogs的示範也可看出,善用class variable可以在各個instance object間取得某程程度的連繫。

3. 資料型別 II

3.1. Dict

  1. collections.ChainMap

    collections.ChainMap可以把多個dict集結串成單一個dict,如

     1: import collections
     2: 
     3: dict1 = {'one':1, 'two': 2}
     4: dict2 = {'two':'貳', 'three': 'III', 'four': 4}
     5: chain = collections.ChainMap(dict1, dict2)
     6: 
     7: print(chain)
     8: print(chain['two'])
     9: chain['five'] = 5
    10: print(chain)
    
    ChainMap({'one': 1, 'two': 2}, {'two': '貳', 'three': 'III', 'four': 4})
    2
    ChainMap({'one': 1, 'two': 2, 'five': 5}, {'two': '貳', 'three': 'III', 'four': 4})
    

    要留意的是:若對ChainMap做新增、修改、刪除,則只會影響裡面的第一個dict。

3.2. types.MappingProxyType

透過MappingProxyType class可以把dict變為唯讀。

1: from types import MappingProxyType
2: 
3: writable = {'one': 1, 'two': 2}
4: writable['three'] = 3
5: readOnly = MappingProxyType(writable)
6: readOnly['three'] = 4 #TypeError: 'mappingproxy' object does not support item assignment
7: readOnly['four'] = 4 #TypeError: 'mappingproxy' object does not support item assignment

3.3. array.array

若要和C語言程式交換資料,或是只需要一個儲存數值型態資料的空間,則可用array.array,優點是比list或tuple省空間。array.array的method與list大致相同,許多情況下甚至可直接交換二者的資料型別而無需修改相關程式碼。

1: import array
2: arr = array.array('f', (1.0, 3.5, 2.0, 6.1))
3: print(arr)
4: print(arr[1])
5: arr.append(2.1)
6: del arr[1]
7: print(arr)
array('f', [1.0, 3.5, 2.0, 6.099999904632568])
3.5
array('f', [1.0, 2.0, 6.099999904632568, 2.0999999046325684])

3.4. Record

同樣以儲存car record(color, mileage, automatic)來比較

  1. list/tuple
    • 建立速度快、執行效率高,
    • 但沒有欄位名稱,建立資料時可能弄錯順序
    • 很難檢查兩個資料物件是否有一致的欄位
    1: car1 = ['red', 3812, True] #list, 可變
    2: car2 = ('blue', 3123, False) #tuple, 不可變
    3: car3 = (343, 'black', True, 'James') #順序錯誤、欄位也不一致
    
  2. dict
    • 可透過key/value來當成欄位名稱
    • 仍缺乏欄位監控機制,防止使用者在建立object時打錯或漏打欄位
    1: Car = {
    2:     'color': 'red',
    3:     'mileage': 3213,
    4:     'automatic': True
    5: }
    6: print(Car)
    7: print(Car['color'])
    8: Car['windshield'] = 'broken'
    9: print(Car)
    
    {'color': 'red', 'mileage': 3213, 'automatic': True}
    red
    {'color': 'red', 'mileage': 3213, 'automatic': True, 'windshield': 'broken'}
    
  3. 自訂類別
    • 麻煩,在要__init__建構子內設定每個欄位,還要自己寫__repr__ method
    1: class Car:
    2:     def __init__(self, color, mileage):
    3:         self.color = color
    4:         self.mileage = mileage
    5: 
    6: car = Car('red', 1234)
    7: car.mileage = 4343 #可修改欄位內容
    8: car.automatic = True #可新增欄位
    
  4. typing.NamedTuple
    • readonly
    • 欄位型別無強制性
     1: from typing import NamedTuple
     2: 
     3: class Car(NamedTuple):
     4:     color: str
     5:     mileage: float
     6:     automatic: bool
     7: 
     8: car = Car('red', 3123, True)
     9: print(car)
    10: #car.mileage = 3123 # 不能變更欄位內容
    11: #car.windshield = 'broken' #不能新增欄位
    12: car2 = Car('blue', 'test', 1234)
    13: print(car2)
    
    Car(color='red', mileage=3123, automatic=True)
    Car(color='blue', mileage='test', automatic=1234)
    
  5. types.SimpleNamespace
    • 本質上是dict,把dict的key變成class attribute
    • 可以新增、修改、刪除attribute
    • 不若想動用到class,此為簡易替代品
    • 仍缺乏結構
    1: from types import SimpleNamespace
    2: 
    3: car = SimpleNamespace(color = 'red',
    4:                       mileage = 1341,
    5:                       automatic = True)
    6: 
    7: car.color = 'blue'
    8: print(car)
    
    namespace(color='blue', mileage=1341, automatic=True)
    
  6. dataclass (Python 3.7+)
    • 能自動建立__init__、__repr__等method
    • 可加入自訂method
    • 可繼承
    • 可設為read only(在@dataclass後加入參數 frozen=True)
     1: from dataclasses import dataclass
     2: 
     3: @dataclass
     4: class Car:
     5:     color: str
     6:     mileage: float
     7:     automatic: bool
     8: 
     9:     def mileage_km(self): #可自訂method
    10:         return self.mileage * 1.609
    11: 
    12: car = Car('red', 3123, True)
    13: print(dir(car))
    14: print(car.mileage_km)
    15: print(car.__repr__)
    16: 
    17: @dataclass
    18: class ElectronicCar(Car): #繼承
    19:     charge: float = 0.0
    20: 
    21: car2 = ElectronicCar('white', 3123, True, 1000)
    22: print(car2)
    23: 
    
    ['__annotations__', '__class__', '__dataclass_fields__', '__dataclass_params__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'automatic', 'color', 'mileage', 'mileage_km']
    <bound method Car.mileage_km of Car(color='red', mileage=3123, automatic=True)>
    <bound method __create_fn__.<locals>.__repr__ of Car(color='red', mileage=3123, automatic=True)>
    ElectronicCar(color='white', mileage=3123, automatic=True, charge=1000)
    
    • __slots__

      如果你的程式會建立同一類別的大量object,但不會增刪object attribute,則可以考慮使用__slots__來節省object占用的記憶體,同時加快存取速度。

       1: from dataclasses import dataclass
       2: 
       3: @dataclass
       4: class Car:
       5: 
       6:     __slots__ = ['color', 'mileage', 'automatic']
       7: 
       8:     color: str
       9:     mileage: float
      10:     automatic: bool
      

      __slots__不限於dataclass,在任何class均可使用

  7. struct.struct
    • 與C的struct交換資料的管道
    1: from struct import Struct
    2: 
    3: MyStruct = struct('1?f') #指定資料格式
    4: data = MyStruct.pack(23, False, 42.0) #資料打包成二進位資料
    5: print(data)
    6: x = MyStruct.unpack(data)
    7: print(x)
    

3.5. Set

  • Python建立set,除了用{}、也可以用set comprehension
  • 若要建立empty set,一定要call set()建構子,因為空的{}會被當成dict
1: vowels = {'a', 'e', 'i', 'o', 'u'}
2: squares = { x ** 2 for x in range(10) }
3: empty = set()
4: 
5: print(vowels)
6: print(squares)
7: print(empty)
{'a', 'o', 'i', 'u', 'e'}
{0, 1, 64, 4, 36, 9, 16, 49, 81, 25}
set()
  1. frozenset
    • frozenset class為set的不可變版本,一旦建立就無法增刪修,只能查詢
    • 但frozenset為靜態iterable object,故為hashable object,能當成dict object的key或其他set的element,這是一般set無法做到的事
    1: vowels = frozenset({'a', 'e', 'i', 'o', 'u'})
    2: d = {vowels: 'hello'} # set變為dict的key
    3: print(d)
    4: 
    
    {frozenset({'e', 'o', 'u', 'a', 'i'}): 'hello'}
    
  2. collections.Counter
    • collections module底下的Counter實作了「多重集合」(multisets),能輸入多個iterable object(set, dict, string)並統整各元素的出現次數
    • Counter實際為dict的subclass,當我們把新的iterable object輸入Counter object時,該iterable object的element會新增為dict的key,而該element出現次數則為dict的value
     1: from collections import Counter
     2: 
     3: inventory = Counter()
     4: loot = {'sward': 1, 'bread': 3}  #裝備品名及數量
     5: inventory.update(loot)
     6: print(inventory)
     7: 
     8: more_loot = ['sward', 'coins']
     9: inventory.update(more_loot)
    10: 
    11: yet_more_loot = ['sward', 'bread', 'drink']
    12: inventory.update(yet_more_loot)
    13: print(inventory)
    
    Counter({'bread': 3, 'sward': 1})
    Counter({'bread': 4, 'sward': 3, 'coins': 1, 'drink': 1})
    

3.6. Stack

幾種實作Stack的結構

  1. list
    • python內部以動態陣列來實作list,所以會配置比element所需更大的空間,視需要增減。故push與pop不見會需要調整大小,平均效率可達O(1)
    • 但為了達到這種效率,element必須從tail做push, pop
    • 若改由list的head做push,pop,則list內所有element均需位移,效率會降為O(n)
     1: a = []
     2: a.append('eating')
     3: a.append('sleeping')
     4: a.append('coding')
     5: print(a)
     6: a.pop()
     7: print(a)
     8: 
     9: #自head做push, pop
    10: a.insert(0, 'drinking')
    11: a.insert(0, 'playing')
    12: a.pop(0)
    13: print(a)
    
    ['eating', 'sleeping', 'coding']
    ['eating', 'sleeping']
    ['drinking', 'eating', 'sleeping']
    
  2. collections.deque
    • 快速穩定的stack
    • 支援從head,tail做push,pop,效率均為O(1)
    • 若要取出中間element,效率為O(n)
     1: from collections import deque
     2: 
     3: a = deque()
     4: 
     5: a.append('eating')
     6: a.append('sleeping')
     7: a.append('coding')
     8: print(a)
     9: a.pop()
    10: print(a)
    11: 
    12: #自中間取值,效能差
    13: print(a[1])
    14: 
    
    deque(['eating', 'sleeping', 'coding'])
    deque(['eating', 'sleeping'])
    sleeping
    
  3. queue.LifoQueue
    • 可用於multithreadin進行parallelism平行運算時的共享資料
    • 這些資料結構還提供了上鎖機制,好讓同一時間只有一個thread能從queue取出資料
    • queue module裡的LifoQueue class雖名為queue,但採Last In, First Out(LIFO),運作上與Stack相同
    1: from queue import LifoQueue
    2: s = LifoQueue()
    3: s.put('eating')
    4: s.put('sleeping')
    5: s.put('coding')
    6: print(s.get())
    7: print(s.get())
    
    coding
    sleeping
    

3.7. Queue

幾種實作queue的資料結構

  1. list
    • 非常慢,沒有效率
  2. collections.deque
    • 快速穩定
     1: from collections import deque
     2: q = deque()
     3: q.append('eating')
     4: q.append('sleeping')
     5: q.append('coding')
     6: q.append('writing')
     7: print(q)
     8: print(q.popleft())
     9: print(q.popleft())
    10: print(q)
    11: 
    
    deque(['eating', 'sleeping', 'coding', 'writing'])
    eating
    sleeping
    deque(['coding', 'writing'])
    
  3. queue.Queue
    • 和queue.LifoQueue一樣,內建上鎖機制,可以用來讓multithreading共享資料或任務
  4. multiprocessing.Queue
    • multi-thread在python中其實不算真正的平行運算,各thread實際上是在同一個interpreter下執行,只用到一個core,藉由不斷切換的方式來達到平行運算的效果。
  5. priority queue
    • 不遵循FIFO原則,而是以priority為順序考量
    • priority由totally-ordered key來決定
  6. heapq
    • 借用list實作的binary tree(heap)結構
    • 可實作priority
    • 可於O(log n)的時間完成插入元素或取出最小元素
    • heapq的各note實際上是存在一個list中;node在list中是依binary tree的順序儲存
    • 借由binary tree所依據的sort key即可實作出priority queue
    • 限制之一是預設為由小到大排序

    heapq.png

    Figure 1: heapq

    1: import heapq
    2: 
    3: q = []
    4: heapq.heappush(q, (2, 'coding'))
    5: heapq.heappush(q, (1, 'eating'))
    6: heapq.heappush(q, (3, 'sleeping'))
    7: while q:
    8:     next_item = heapq.heappop(q)
    9:     print(next_item)
    
    (1, 'eating')
    (2, 'coding')
    (3, 'sleeping')
    
  7. queue.PriorityQueue
    • 可用於multi-thread的heapq
      **

4. LOOP

4.1. enumerate()

  • 如果想保留for-each的寫法,但同時又希望能取得每個element的index
1: my_items = ['a', 'b', 'c']
2: print(list(enumerate(my_items)))
3: for i, item in enumerate(my_items):
4:     print(f'{i}: {item}')
[(0, 'a'), (1, 'b'), (2, 'c')]
0: a
1: b
2: c

4.2. zip()

  • 可用來同時走訪多個container
  • 可用來旋轉二維list
1: my_items = ['a', 'b', 'c']
2: my_no = [1, 2, 3]
3: print(list(zip(my_no, my_items)))
4: 
5: for no, item in zip(my_no, my_items):
6:     print(f'{no}: {item}')
[(1, 'a'), (2, 'b'), (3, 'c')]
1: a
2: b
3: c
  1. 旋轉list
    1: a = [1, 2, 3]
    2: b = [4, 5, 6]
    3: c = [7, 8, 9]
    4: matrix = [a, b, c]
    5: print(matrix)
    6: print(list(zip(*matrix)))
    7: print(list(zip(*reversed(matrix))))
    
    [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    [(1, 4, 7), (2, 5, 8), (3, 6, 9)]
    [(7, 4, 1), (8, 5, 2), (9, 6, 3)]
    

4.3. comprehension

  • 即簡單的單行for-loop
  • 可以讓我們用單行程式產生出包含特定元素的list, set, dict
  1. 語法
    1: list = [運算式 for 變數 in iterable object]
    
  2. list comprehension
    1: squares = [x ** 2 for x in range(10)]
    2: print(squares)
    
    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    
  3. comprehension過濾條件
    1: list = [x ** 2 for x in range(10) if x % 2 == 0]
    2: print(list)
    
    [0, 4, 16, 36, 64]
    
  4. set與dict comprehension
    1: setA = [x ** 2 for x in range(10)]
    2: print(setA)
    3: dicA = {x: x ** 2 for x in range(10)}
    4: print(dicA)
    
    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
    

4.4. list

  1. slice

    語法

    1: list[index start: index end: step]
    
    1: nums = [1, 2, 3, 4, 5, 6, 7, 8]
    2: print(nums[::])
    3: print(nums[1:6:2])
    4: nums[1:6:2] = [10, 20, 30]
    5: print(nums)
    6: 
    
    [1, 2, 3, 4, 5, 6, 7, 8]
    [2, 4, 6]
    [1, 10, 3, 20, 5, 30, 7, 8]
    
  2. copy
    1: l = list(range(6))
    2: print(l)
    3: cl = l # 指向同一list
    4: dl = l[::] # shallow copy
    5: l[3] = 10
    6: print(cl)
    7: print(dl)
    
    [0, 1, 2, 3, 4, 5]
    [0, 1, 2, 10, 4, 5]
    [0, 1, 2, 3, 4, 5]
    

5. Python 套件管理

5.1. pip v.s. conda

  1. pip or conda
    • Python 的一大優勢之一便是龐大的第三方函式庫,讓使用 python 的程式設計師可以方便的呼叫、進行如網路資料下載解析、資料的視覺化、甚或是大數據的複雜分析與人工智慧的相關套件。,
    • 目前用來管理這些龐大套件的工具主要有二:pip 與 Conda。
  2. pip
    • Pip是Python Packaging Authority推薦、用於從Python Package Index安裝套件的工具,提供了對 Python 套件的搜㝷、下載、安裝、卸載的功能。
    • 若在 python.org 下載最新版本的 python,則已內建 pip 安裝套件。 Python 3.4+ 以上版本均已包括 pip
    • 該工具類似 Linux 下的 apt/yum 或 MAC 下的Homebrew
  3. conda
    • Conda 是一個開源的跨平台工具軟體,它被設計作為 Python、R、Lua、Scala、C/C++、FORTRAN/ 與 Java 等任何程式語言的套件、依賴性以及工作環境管理員,特別受到以 Python 作為主要程式語言的資料科學團隊所喜愛。
    • 適用平台:Windows, macOS, Linux
    • 傳統 Python 使用者以 pip 作為套件管理員(package manager)、以 venv 作為工作環境管理員(environment manager),而 conda 則達成了「兩個願望、一次滿足」既可以管理套件亦能夠管理工作環境。2
  4. In both cases:
  5. difference between conda, anaconda, and miniconda
    • conda is both a command line tool, and a python package. 3
    • Anaconda 發行版會預裝很多套件,而 Miniconda 是最小的 conda 安裝環境, 一個乾淨的 conda 環境。
    • pip 只是運與安裝 python package,而 conda 用來安裝管理任何語言的包。
    • 不一定要安裝 Anaconda 或 Miniconda,也可透過 pip 直接安裝 conda

        pip install conda
      

5.2. conda 安裝與使用

  1. 下載 v.s. 安裝
  2. 移除
    • Windows: uninstall
    • Linux/macOS:
      rm -rf ~/anaconda

5.3. python package 安裝(conda)

  • 安裝 package:
    conda install packageName

      conda install pandas
    
  • 移除 package:
    conda remove packageName

      conda remove pandas
    
  • 安裝特定版本 python
    conda install python=version

      conda install python=3.5
    
  • 了解目前系統可用套件

        conda list
    

5.4. Python 常用函式庫

  1. 爬蟲
    • Scrapy:

      scrapy.jpg

      • Scrapy,Python 開發的一個快速、高層次的 web 數據抓取框架,用於抓取 web 站點並從頁面中提取結構化的數據。Scrapy 用途廣泛,可以用於數據挖掘、監測和自動化測試4
      • Scrapy 吸引人的地方在於它是一個框架,任何人都可以根據需求方便的修改。它也提供了多種類型爬蟲的基類,如 BaseSpider、sitemap 爬蟲等。
    • beautifulsoup4:

      bs.png

      • Beautiful Soup 是一個 Python 的函式庫模組,可以讓開發者僅須撰寫非常少量的程式
        碼,就可以快速解析網頁 HTML 碼,從中翠取出使用者有興趣的資料、去蕪存菁,降低網
        路爬蟲程式的開發門檻、加快程式撰寫速度。5
      • 而 Beautiful Soup 是基於 HTML DOM 的,會載入整個文檔,解析整個 DOM 樹,因此時間和內存開銷都會大很多,所以性能要低於 lxml。6
    • Selenium

      selenium.jpg

      • 原為網頁測試工具,但由於可以直接以程式碼操控瀏覽器的特性,使其成為網路爬蟲必備
        的工具之一。7
      • Selenium 執行「真實的瀏覽器」來進行網站操作的自動化,它能夠直接獲取即時的內容,包括被 JavaScript 修改過的 DOM 內容,讓程式可以直接與網頁元素即時互動、執行 JavaScript 程式,因此也適用於前端採用 AJAX 技術的網站。8
      • Selenium 是許多 Web Testing 工具的核心,利用 Selenium 操作網頁表單資料、點選按鈕或連結、取得網頁內容並進行檢驗,可以滿足相當多測試的需求。
  2. 網站
    • Django

      django.jpg

      • Django (ˈdʒæŋɡoʊ jang-goh) 可以說是 Python 最著名的 Web Framework,一些知名的網站如 Pinterest, Instagram, Disqus 等等都使用過它來開發。9
      • 免費開放原始碼
      • 著重快速開發、高效能
      • 遵從 DRY ( Don’t Repeat Yourself ) 守則,致力於淺顯易懂和優雅的程式碼
      • 使用類似 Model–view–controller (MVC) pattern 的架構
      • 10 Popular Websites Built With Django
    • Flask

      flask.png

      • Flask 是一個使用 Python 撰寫的輕量級 Web 應用程式框架,由於其輕量特性,也稱為
        micro-framework(微框架)。10
      • Flask 和 Django 不同的地方在於 Flask 給予開發者非常大的彈性(當然你也可以說是
        需要思考更多事情),可以選用不同的用的 extension 來增加其功能。
      • 相比之下,Django 雖然完善但技術選擇相對不彈性,不論是 ORM、表單驗證或是模版引
        擎都有自己的作法。
      • 沒有最好的框架,只有合適的使用情境。
  3. 資料處理科學計算
    • Numpy

      numpy.jpg

      • Numpy 底層以 C 和 Fortran 語言實作,所以能快速操作多重維度的陣列。11
      • 當 Python 處理龐大資料時,其原生 list 效能表現並不理想(但可以動態存異質資料),而 Numpy 具備平行處理的能力,可以將操作動作一次套用在大型陣列上。
      • 此外 Python 其餘重量級的資料科學相關套件(例如:Pandas、SciPy、Scikit-learn 等)都幾乎是奠基在 Numpy 的基礎上。
    • Scipy

      scipy.png

      • 科學計算神器
      • Numpy 是以矩陣基礎做數據的數學運算,SciPy 就是以 Numpy 為基礎做科學、工程的運算處理的 package,包含統計、優化、整合、線性代數、傅立葉轉換圖像等較高階的科學運算。12
    • Pandas

      pandas.png

      • 建構在 NumPy 之上,提供資料結構與資料處理工具,讓資料清理與分析更為快速與方便
      • 適合處理表格或異質資料 (NumPy 適合處理同質之數值陣列資料)
  4. 視覺化
    • matplotlib

      matplotlib.jpg

      • Matplotlib 就是 MATLAB+Plot+Library 的簡稱,因為是模仿 MATLAB 建立的繪圖庫,所以繪
        圖風格會與 MATLAB 有點類似。13
      • 為了提高處理大量資料的性能,Matplotlib 大量使用了 NumPy 和其相關的擴展代碼。 為了方便快速繪圖, Matplotlib 通過 pyplot 模組提供了一套和 MATLAB 類似的繪圖 API,只需要調用 pyplot 模組所提供的函數,就可以實現快速繪圖及設置圖表的各種細節。
      • 另一方面 Matplotlib 也適合互動式繪製圖表,可以很方便地處理二維和三維的圖表。
    • seaborn

      seaborn.png

      seabornPlots.jpg

      • 散點圖矩陣神器
      • Seaborn 是 Python 一個著名的數據視覺化的 package, 以 matplotlib 為基底的一個擴
        展包, 它提供了易於理解的統計圖和便利的繪圖指令14
      • seaborn 庫是對 matplotlib 庫更高級別的封裝,相當於提供了各種統計圖的模板15
    • ggplot

      ggplot.jpg

      • R 語言視覺化神器的 Python 版本
      • ggplot2 是一個十分強大的 R 語言可視化包。它的核心理念是將繪圖與數據分離,數據相關
        的繪圖與數據無關的繪圖分離。16
      • 它是按圖層作圖的,一個語句做一個僅包含基礎作圖單元的圖層,然後通過不同圖層的疊
        加最後成圖。
    • plotly

      plotly.png

      • 這個神器是個 js 庫,不過也有各種流行的語言介面
      • plotly 是一個能讓你畫出互動式圖表的一個開源套件,其基本操作是免費的,如果你想要使用網頁的版本,可以使用更多 plotly 的進階功能的話,就必須付費
  5. 機器學習
    • scikit-learn

      scikit-learn.png

      • 幾乎所有機器學習演算法都囊括
      • scikit-learn,又寫作 sklearn,是一個開源的基於 python 語言的機器學習工具包。它通過 NumPy, SciPy 和 Matplotlib 等 python 數值計算的庫實現高效的算法應用,並且涵蓋了幾乎所有主流機器學習算法。17
      • sklearn 中常用的 module 有分類、回歸、聚類、降維、模型選擇、預處理。
    • NLTK

      NLTK.png

      • NLTK 全文是 “Nature Language Tool Kit” (NLTK),是 Python 中一個經典的、專門用於進行自然語言處理的工具。18
      • 雖然也能進行部份中文的處理,但是對於中文的支援度自然沒有英文來得好
      • 目前而言,繁體中文有兩個套件可以使用,一個是中研院開發的斷詞系統,另一套系統為
        jieba(結巴)。
      • 少女詩人小冰
    • TensorFlow

      tensorflow.jpg

      • Tensorflow 最初為 Google Brian 所開發。在 2015 時,Google 將之開源,為現今重要的深
        度學習框架之一,它支援各式不同的深度學習演算法,並已應用於各大企業服務上,Ex:
        Google, Youtube, Airbnb, Paypal … 等。19
      • 此外,Tensorflow 也支援在各式不同的
        device 上運行深度學習 Ex: Tensorflow Lite 、 Tensorflow.js 等等。
      • Tensorflow 為目
        前最受歡迎的機器學習、深度學習開源專案。不管是 github fork 的數量、論文的使用次
        數以及熱門程度,均比其他的框架來的多20

      深度學習套件

    • Keras

      keras.png

      • 在 2015 年 TensorFlow 推出的同時,美國麻省理工學院(又是它,這學校開門就能賺錢)
        也推出一套能很容易被使用者透過 Python 寫 Deep learning 的應用程式介面 API ,叫
        做 Keras 。
      • Keras 只有介面喔! Keras 本身還是得透過 TensorFlow ,或者其
        他像是微軟的 CNTK 這類引擎當作底層,才能執行
        21
      • Keras 使用上比較接近人類的想法( TensorFlow 設計上沒有錯,只不過比較是針對電腦
        系統跟網路通訊的想法),所以透過 Python 呼叫 Keras 能夠很簡單地描述我們人類想
        要電腦達到 Deep Learning 要做的事情。目前在教學上, Keras 的普及率相當高
      • Keras 可以快速有方便運算的主要原因是,它已經將訓練模型的輸入層、隱藏層、輸出層,做好架構,使用者只需要加入並且填寫正確的參數 ex.神經元個數、activation function 的函式…等。22
    • PyTorch

      pytorch.jpg

      • Numpy 的 GPU 版
      • PyTorch 為 Facebook 在 2017 年初開源的深度學習框架,其建立在 Torch 之上,且標
        榜 Python First ,為量身替 Python 語言所打造,使用起來就跟寫一般 Python 專案沒
        兩樣,也能和其他 Python 套件無痛整合。PyTorch 的優勢在於其概念相當直觀且語法簡
        潔優雅,因此視為新手入門的一個好選項;再來其輕量架構讓模型得以快速訓練且有效運
        用資源。23
      • 於 PyTorch 框架中,資料類型定義為張量(Tensor),張量可以是純量、向量或是更高維度的矩陣,而 torch 函式庫負責張量在 CPU/GPU 的運算。

5.5. python 執行環境建立與維護

  • 建立24

    conda create -n envName
    
  • 啟用

    conda activate envName
    
  • 退出

     conda deactiveate
    
  • 刪除

    conda env remove -n envName
    
  • 列出目前系統中所有的虛擬環境

    conda env list
    

5.6. 將執行環境匯入 jupyter

  • 於終端機(for windows: Anaconda prompt)下建立、啟用所需虛擬環境
  • 將環境滙至 jupyter kernel
python -m ipykernel install --user --name 虛擬環境名稱 --display-name "在jupyter中的名稱"
  • 啟動 jupyter

6. NumPy

6.1. 關於 NumPy

  • Numpy 是 Python 的一個重要模組,主要用於資料處理上。Numpy 底層以 C 和 Fortran 語言實作,所以能快速操作多重維度的陣列。25
  • 當 Python 處理龐大資料時,其原生 list 效能表現並不理想(但可以動態存異質資料),而 Numpy 具備平行處理的能力,可以將操作動作一次套用在大型陣列上。
  • Python 多數重量級的資料科學相關套件(例如:Pandas、SciPy、Scikit-learn 等)都幾乎是奠基在 Numpy 的基礎上。因此學會 Numpy 對於往後學習其他資料科學相關套件打好堅實的基礎。
  • 延伸閱讀: 為什麼要用 Numpy?

6.2. 安裝模組

1: pip install numpy
  1. PyCharm

    2023-02-13_10-26-12_2023-02-13_10-24-46.png

    Figure 2: 標題

  2. Colab
    1: !pip install numpy
    

6.3. 匯入模組

  • 使用模組裡的函式要加模組名稱
import numpy
  • 匯入 numpy 模組並使用 np 作為簡寫,這是 Numpy 官方倡導的寫法
import numpy as np
  • Numpy 中的多維資料型別稱為 ndarray

6.4. Create ndarray

  • NumPy官網
  • Numpy 的重點在於陣列的操作,其所有功能特色都建築在同質且多重維度的 ndarray(N-dimensional array)上。
  • ndarray 的關鍵屬性是維度(ndim)、形狀(shape)和數值類型(dtype)。 一般我們稱一維陣列為 vector 而二維陣列為 matrix25
  • axis 0, axis 1, axis 2:
    一維時 axis 0為x軸
    二維時 axis 0為y軸
  1. 一維陣列
    • 可以將python的list 或 tuple 轉成NumPy Array
      import numpy as np
      np1 = np.array( [1, 2, 3, 4] )
      print(np1)
      
      [1 2 3 4]
      
    • 使用 np.arange( ) 方法
      import numpy as np
      
      np2 = np.arange(5)
      print("=====np2=====")
      print(np2)
      np3 = np.arange(1, 4, 0.5)
      print("=====np3=====")
      print(np3)
      
      =====np2=====
      [0 1 2 3 4]
      =====np3=====
      [1.  1.5 2.  2.5 3.  3.5]
      
    • np.arange() v.s. range()

      差異:

      • range()為 python 內建函數
      • range() return 的是 range object,而 np.nrange() return 的是 numpy.ndarray()
      • range()不支援 step 為小數,np.arange()支援 step 為小數
  2. 二維陣列
    import numpy as np
    
    np4 = np.array( [[1, 2, 4], [3, 4, 5]] )
    print("shape:", np4.shape)
    print("np4:\n", np4)
    print("取出第0列:",np4[0])
    print("取出第1行:",np4[:,1])
    np5 = np.array([np.arange(3), np.arange(3)])
    print('np5:\n', np5)
    
    np6 = np.arange(8).reshape(2, 4)
    print('np6:\n', np6)
    
      shape: (2, 3)
      np4:
       [[1 2 4]
       [3 4 5]]
      取出第0列: [1 2 4]
      取出第1行: [2 4]
      np5:
       [[0 1 2]
       [0 1 2]]
      np6:
       [[0 1 2 3]
       [4 5 6 7]]
    
  3. 多維陣列
    import numpy as np
    
    np7 = np.arange(24).reshape(2, 3, 4)
    print('np7:\n',np7)
    
    np8 = np.arange(13, 60, 2).reshape(2, 3, 4)
    print("np8:\n", np8)
    
      np7:
       [[[ 0  1  2  3]
        [ 4  5  6  7]
        [ 8  9 10 11]]
    
       [[12 13 14 15]
        [16 17 18 19]
        [20 21 22 23]]]
      np8:
       [[[13 15 17 19]
        [21 23 25 27]
        [29 31 33 35]]
    
       [[37 39 41 43]
        [45 47 49 51]
        [53 55 57 59]]]
    
  4. 隨機矩陣
    import numpy as np
    
    np8 = np.random.random((3, 2)) #矩陣大小以tuple表示
    print('np8:\n', np8)
    # 四個人擲骰子,每人擲兩次
    np9 = np.random.randint(1, 7, size=[4, 2]) #矩陣大小以list表示
    print('np9:\n', np9)
    
    np8:
     [[0.7263954  0.71063088]
     [0.07725825 0.11562424]
     [0.57923875 0.85345365]]
    np9:
     [[5 6]
     [2 2]
     [5 5]
     [5 2]]
    
  5. 0/1 矩陣
    • np.zeros: np.zeros( (陣列各維度大小用逗號區分) ):建立全為 0 的陣列,可以小括號定義陣列的各個維度的大小
    import numpy as np
    
    zeros = np.zeros( (3, 5) )
    print("zeros=>\n{0}".format(zeros))
    
    zeros=>
    [[0. 0. 0. 0. 0.]
     [0. 0. 0. 0. 0.]
     [0. 0. 0. 0. 0.]]
    
    • np.ones: np.ones( (陣列各維度大小用逗號區分) ):用法與 np.zeros 一樣
    import numpy as np
    
    ones = np.ones( (4, 3) )
    print("oness=>\n{0}".format(ones))
    
    oness=>
    [[1. 1. 1.]
     [1. 1. 1.]
     [1. 1. 1.]
     [1. 1. 1.]]
    
  6. TODO 型態轉換

    astype

  7. TODO 補入教材

6.5. Numpy計算時間比較

(setq org-babel-python-command "ipython --no-banner --classic --no-confirm-exit")

自行比較以下兩個版本的執行時間

ar = np.arange(1000)
%timeit ar**3
1: %timeit [i**3 for i in range(1000)]
import numpy as np
ar = np.arange(1000)
%timeit ar**3

6.6. [課堂練習]

  • 模擬一個37人/7科的全班月考成績(隨機生成)

    1 : 33  66  29
    2 : 86  1   11
    3 : 84  66  81
    4 : 76  38  16
    5 : 13  27  77
    6 : 88  14  47
    7 : 45  70  35
    8 : 94  79  98
    

6.7. 矩陣運算

  1. 矩陣變形:reshape()
    1. reshape()
    2. transpose()
     1: import numpy as np
     2: n1 = np.random.random((12)) #矩陣大小以tuple表示
     3: 
     4: n2 = n1.reshape(3, 4)
     5: print(n1)
     6: print(n2)
     7: n3 = n2.transpose()
     8: print(n2)
     9: print(n3)
    10: n4 = n3.flatten()
    11: print(n4)
    
    [0.7338917  0.94396356 0.58422402 0.02469613 0.24878891 0.32737463
     0.69404152 0.48386995 0.45738145 0.56101421 0.54547518 0.43416765]
    [[0.7338917  0.94396356 0.58422402 0.02469613]
     [0.24878891 0.32737463 0.69404152 0.48386995]
     [0.45738145 0.56101421 0.54547518 0.43416765]]
    [[0.7338917  0.94396356 0.58422402 0.02469613]
     [0.24878891 0.32737463 0.69404152 0.48386995]
     [0.45738145 0.56101421 0.54547518 0.43416765]]
    [[0.7338917  0.24878891 0.45738145]
     [0.94396356 0.32737463 0.56101421]
     [0.58422402 0.69404152 0.54547518]
     [0.02469613 0.48386995 0.43416765]]
    [0.7338917  0.24878891 0.45738145 0.94396356 0.32737463 0.56101421
     0.58422402 0.69404152 0.54547518 0.02469613 0.48386995 0.43416765]
    
  2. 索引(Indexing)、切片(Slicing)

    索引(Indexing)的用途不外乎就是為了要從陣列和矩陣中取值,但除此之外有很多種功能!可以取出連續區間,還可以間隔取值!26

    • 選取連續區間 [a:b]
      1: import numpy as np
      2: a = np.arange(10) ** 2
      3: print("a=> {0}".format(a))
      4: print("a[2:5]=> {0}".format(a[2:5]))
      
      a=> [ 0  1  4  9 16 25 36 49 64 81]
      a[2:5]=> [ 4  9 16]
      
    • 間隔選取[::c]

      以1維陣列來說明x[a:b:c]

      • a:選取資料的起始索引
      • b:選取資料的結束索引+1
      • c:選取資料間隔,以索引值可以被此值整除的元素,不指定表示1
      1: import numpy as np
      2: a = np.arange(10) ** 2
      3: print("a==> {0}".format(a))
      4: a[2:9:3] = 999
      5: print("a==> {0}".format(a))
      
      a==> [ 0  1  4  9 16 25 36 49 64 81]
      a==> [  0   1 999   9  16 999  36  49 999  81]
      
  3. 取出特定行列、特定範圍
    • 取出第x列: Ary[x]
    • 取出第x行: Ary[:,x]
    • 取出第x_{1}~x_{2}列、第y_{1}~y_{2}行的範圍: Ary[x_{1}:x_{2} + 1, y_{1}:y_{2} + 1]
    1: import numpy as np
    2: a = np.arange(12).reshape(3, 4)
    3: print(a)
    4: print(a[1])
    5: print(a[:,1])
    6: print(a[1:3, 1:3])
    
    [[ 0  1  2  3]
     [ 4  5  6  7]
     [ 8  9 10 11]]
    [4 5 6 7]
    [1 5 9]
    [[ 5  6]
     [ 9 10]]
    
  4. 刪除行/列

    np.delete(temp,0,axis=1)#temp為操作物件,0表示要刪除的物件索引,axis表示行還是列,axis=0表示刪除行,axis=1表示刪除列。

     1: import numpy as np
     2: 
     3: a = np.arange(12).reshape(3, 4)
     4: print(a)
     5: a_delCol1 = np.delete(a, 1, 0)
     6: print('刪除第1行')
     7: print(a_delCol1)
     8: a_delRow1 = np.delete(a, 1, 1)
     9: print('刪除第1列')
    10: print(a_delRow1)
    11: print('現在的a')
    12: print(a)
    
    [[ 0  1  2  3]
     [ 4  5  6  7]
     [ 8  9 10 11]]
    刪除第1行
    [[ 0  1  2  3]
     [ 8  9 10 11]]
    刪除第1列
    [[ 0  2  3]
     [ 4  6  7]
     [ 8 10 11]]
    現在的a
    [[ 0  1  2  3]
     [ 4  5  6  7]
     [ 8  9 10 11]]
    
  5. 迭代(輸出)

    如果是要對整個矩陣的值做運算,無需使用迴圈

    • 一維陣列
      1: import numpy as np
      2: a = np.arange(4) ** 2
      3: print("a: ",a)
      4: for i in a:
      5:     print("a**(1/2)=> {0}".format(np.round(i**(1/2), 0)))
      
      ('a: ', array([0, 1, 4, 9]))
      a**(1/2)=> 1
      a**(1/2)=> 1
      a**(1/2)=> 1
      a**(1/2)=> 1
      
    • 多維陣列: 多維陣列在for loop中取值時,會以第一維度為優先!
      1: import numpy as np
      2: a = np.arange(1, 41).reshape(5, 8)
      3: for row in a:
      4:     for i in row:
      5:         print("{0:3d}".format(i), end='')
      6:     print()
      7: 
      
       1  2  3  4  5  6  7  8
       9 10 11 12 13 14 15 16
      17 18 19 20 21 22 23 24
      25 26 27 28 29 30 31 32
      33 34 35 36 37 38 39 40
      
  6. 基礎運算
    • 維度相同的矩陣相加、減
      import numpy as np
      a = np.array( [6, 7, 8, 9] )
      b = np.arange( 4 )
      c = a - b
      print("a=>{0}".format(a))
      print("b=>{0}".format(b))
      print("c=>{0}".format(c))
      
      a=>[6 7 8 9]
      b=>[0 1 2 3]
      c=>[6 6 6 6]
      
    • 矩陣與常數運算
      import numpy as np
      import math
      a = np.random.randint(100, size=(2, 4)) #矩陣大小以tuple表示
      
      b = a + 10
      c = a**2
      print("a=>{0}".format(a))
      print("b=>{0}".format(b))
      print("c=>{0}".format(c))
      
      a=>[[51 74 98 37]
       [13 13 74 86]]
      b=>[[ 61  84 108  47]
       [ 23  23  84  96]]
      c=>[[2601 5476 9604 1369]
       [ 169  169 5476 7396]]
      
  7. 矩陣轉置

    \[a=\begin{bmatrix}1&0\\2&3\end{bmatrix}, a^T=\begin{bmatrix}1&2\\0&3\end{bmatrix}\]

    import numpy as np
    a = np.array([[1, 0],
                  [2, 3]])
    print(a)
    print('--Matrix transpose--')
    print(a.transpose())
    
    [[1 0]
     [2 3]]
    --Matrix transpose--
    [[1 2]
     [0 3]]
    
  8. 矩陣相乘
    • 矩陣乘法

      \[a=\begin{bmatrix}a_{11}&a_{12}\\a_{21}&a_{22}\end{bmatrix}, b=\begin{bmatrix}b_{11}&b_{12}&b_{13}\\b_{21}&b_{22}&b_{23}\end{bmatrix}\]
      \[a \cdot b=\begin{bmatrix}a_{11}*b_{11}+a_{12}*b_{21}&a_{11}*b_{12}+a_{12}*b_{22}&a_{11}*b_{13}+a_{12}*b_{23}\\a_{21}*b_{11}+a_{22}*b_{21}&a_{21}*b_{12}+a_{22}*b_{22}&a_{21}*b_{13}+a_{22}*b_{23}\end{bmatrix}\]

      import numpy as np
      A = np.array([[1, 2, 3], [4, 3, 2]])
      B = np.array([[1, 2], [2, 0], [3, -1]])
      print("{0}".format(A.dot(B)))
      
      [[14 -1]
       [16  6]]
      
    • 相對位置乘法

      \[a=\begin{bmatrix}a_{11}&a_{12}\\a_{21}&a_{22}\end{bmatrix}, b=\begin{bmatrix}b_{11}&b_{12}\\b_{21}&b_{22}\end{bmatrix}\]
      \[a \cdot b=\begin{bmatrix}a_{11}*b_{11}&a_{12}*b_{12}\\a_{21}*b_{21}&a_{22}*b_{22}\end{bmatrix}\]

      import numpy as np
      A = np.array([[1, 2], [4, 5]])
      B = np.array([[7, 8], [9, 10]])
      print("A:\n{0}".format(A))
      print("B:\n{0}".format(B))
      print("A*B:\n{0}".format(A*B))
      
      
      A:
      [[1 2]
       [4 5]]
      B:
      [[ 7  8]
       [ 9 10]]
      A*B:
      [[ 7 16]
       [36 50]]
      
    • 取代矩陣中元素

      這裡也可以看出NumPy對於選取矩陣中元素的極好彈性,可直接以條件來當成選取方式

      import numpy as np
      
      C = np.array([5, -1, 3, 9, 0])
      print(C<=0)
      # 將矩陣中小於等於0的元素取代為0;其他轉為1
      C[C<=0] = 0
      C[C>0] = 1
      print(C)
      
      [False  True False False  True]
      [1 0 1 1 0]
      
    • [課堂練習]
      • 模擬一個37人/7科的全班月考成績(隨機生成, 0~100)
      • 將所有 55<=分數<60 的成績均改為60
  9. 反矩陣

    AB=BA=I, 其中 I 為單位矩陣

    import numpy as np
    
    A = np.array([[4, -7], [2, -3]])
    print("A:\n", A)
    B = np.linalg.inv(A)
    print("B:\n", B)
    print("A dot B:\n", A.dot(B))
    
    A:
     [[ 4 -7]
     [ 2 -3]]
    B:
     [[-1.5  3.5]
     [-1.   2. ]]
    A dot B:
     [[1. 0.]
     [0. 1.]]
    
  10. 合併矩陣
    • vstack
      import numpy as np
      a = np.ones((2, 2))
      b = np.zeros(2)
      print(a)
      print(b)
      c = np.vstack((a, b))
      print(c)
      
      [[1. 1.]
       [1. 1.]]
      [0. 0.]
      [[1. 1.]
       [1. 1.]
       [0. 0.]]
      
    • hstack
      import numpy as np
      a = np.ones((2, 2))
      b = [[3],
           [4]]
      print(a)
      print(b)
      c = np.hstack((a, b))
      print(c)
      
      
      [[1. 1.]
       [1. 1.]]
      [[3], [4]]
      [[1. 1. 3.]
       [1. 1. 4.]]
      

6.8. 矩陣函數

  1. 官網
  2. numpy.sum()
    • 語法
      numpy.sum(a, axis=None, dtype=None, out=None, keepdims=<no value>, initial=<no value>, where=<no value>)
      

      其他參數用法詳見官方網站

    • 範例
      import numpy as np
      
      np_array_2x3 = np.array([[0,2,4],[1,3,5]])
      print('=====Array=====')
      print(np_array_2x3)
      print('=====列=====')
      print(np.sum(np_array_2x3, axis = 0))
      print('=====行=====')
      print(np.sum(np_array_2x3, axis = 1))
      print('=====陣列總和1=====')
      print(np.sum(np_array_2x3))
      print('=====陣列總和2=====')
      print(np_array_2x3.sum())
      
      
      =====Array=====
      [[0 2 4]
       [1 3 5]]
      =====列=====
      [1 5 9]
      =====行=====
      [6 9]
      =====陣列=====
      15
      =====陣列=====
      15
      
  3. 其他常用函數
    • min()
    • max()
    • argmin
    • mean()
    • std()
    • var()
    • sqrt()
    • size()
    • dtype()
    • itemsize()
    import numpy as np
    
    np1 = np.random.randint(0, 10, size=[3, 2])
    print("np\n", np1)
    print("np1.sum", np1.sum())
    print("sum:", sum(np1))
    print("sum:", sum(np1,3))
    print("min:", np1.min())
    print("max:", np1.max())
    print("mean:", np.mean(np1))
    
    np
     [[0 0]
     [0 3]
     [0 3]]
    np1.sum 6
    sum: [0 6]
    sum: [3 9]
    min: 0
    max: 3
    mean: 1.0
    
  4. sum() v.s. np.sum()

    2sum.png

    Figure 3: sum() v.s. np.sum()

  5. numpy.max()
    • 語法
      numpy.max(a, axis=None, out=None, keepdims=False)
      
      • 求序列的最值
      • 最少接收一個引數
      • axis:預設為列向(也即 axis=0),axis = 1 時為行方向的最值;
    • 範例
      import numpy as np
      
      np_array_2x3 = np.array([[9,2,8],[4,7,5]])
      print('=====Array=====')
      print(np_array_2x3)
      print('=====列=====')
      print(np.max(np_array_2x3, axis = 0))
      print('=====行=====')
      print(np.max(np_array_2x3, axis = 1))
      print('=====陣列=====')
      print(np.max(np_array_2x3))
      
      =====Array=====
      [[9 2 8]
       [4 7 5]]
      =====列=====
      [9 7 8]
      =====行=====
      [9 7]
      =====陣列=====
      9
      
  6. numpy.maxium()
    • 語法
      numpy.maximum:(X, Y, out=None)
      
      • X 與 Y 逐位比較取其大者;
      • 最少接收兩個引數
    • 範例
      import numpy as np
      npA1 = np.array([[9,-9,8],[4,7,5]])
      npA2 = np.array([[0,1,8],[10,-7,5]])
      print("=====npA1=====")
      print(npA1)
      print("=====npA2=====")
      print(npA2)
      print("=====maximum=====")
      print(np.maximum(npA1, npA2))
      
      =====npA1=====
      [[ 9 -9  8]
       [ 4  7  5]]
      =====npA2=====
      [[ 0  1  8]
       [10 -7  5]]
      =====maximum=====
      [[ 9  1  8]
       [10  7  5]]
      
  7. numpy.argmax()
    • 語法
      numpy.argmax(a, axis=None, out=None)[source]¶
      
      • a: 可以轉換為陣列的陣列或物件,我們需要在其中找到最高值的索引。
      • axis: 沿著行(axis=0)或列(axis=1)查詢最大值的索引。預設情況下,通過對陣列進行展平可以找到最大值的索引。
      • out: np.argmax 方法結果的佔位符。它必須有適當的大小以容納結果。Returns the indices of the maximum values along an axis.
    • 範例
      • 一維陣列
        import numpy as np
        
        a=np.array([2,6,1,6])
        
        print("Array:")
        print(a)
        
        req_index=np.argmax(a)
        print("\nIndex with the largest value:")
        print(req_index)
        
        print("\nThe largest value in the array:")
        print(a[req_index])
        
        Array:
        [2 6 1 6]
        
        Index with the largest value:
        1
        
        The largest value in the array:
        6
        
      • 二維陣列
        import numpy as np
        
        a = np.array([[2,1,6],
                    [7,14,5]])
        
        print("Array:")
        print(a)
        
        req_index=np.argmax(a, axis=0)
        print("\nIndex with the largest value(axis=0):")
        print(req_index)
        req_index=np.argmax(a, axis=1)
        print("\nIndex with the largest value(axis=1):")
        print(req_index)
        req_index=np.argmax(a)
        print("\nIndex with the largest value:")
        print(req_index)
        
        
        Array:
        [[ 2  1  6]
         [ 7 14  5]]
        
        Index with the largest value(axis=0):
        [1 1 0]
        
        Index with the largest value(axis=1):
        [2 1]
        
        Index with the largest value:
        4
        

        argSeq.jpg

        Figure 4: Sequence of Arg in array

  8. about axis

    aryaxis.jpg

    Figure 5: Axis in ndarray

  9. tile()

    numpy.tile()是個什麼函數呢,說白了,就是把數組沿各個方向複製

    import numpy as np
    print('=====一維tile=====')
    b = np.array([[1, 2], [3, 4]])
    b1 = np.tile(b, 2)
    print(b1)
    print('=====二維tile=====')
    b2 = np.tile(b, (2, 3))
    print(b2)
    
    =====一維tile=====
    [[1 2 1 2]
     [3 4 3 4]]
    =====二維tile=====
    [[1 2 1 2 1 2]
     [3 4 3 4 3 4]
     [1 2 1 2 1 2]
     [3 4 3 4 3 4]]
    
  10. numpy.allclose
    • Returns True if two arrays are element-wise equal within a tolerance.
    • The tolerance values are positive, typically very small numbers. The relative difference (rtol * abs(b)) and the absolute difference atol are added together to compare against the absolute difference between a and b.
    • NaNs are treated as equal if they are in the same place and if equal_nan=True. Infs are treated as equal if they are in the same place and of the same sign in both arrays.
    • 語法
      numpy.allclose(a, b, rtol=1e-05, atol=1e-08, equal_nan=False)
      
    • Parameters
      • a, b: array_like. Input arrays to compare.
      • rtol: float. The relative tolerance parameter (see Notes).
      • atol: float. The absolute tolerance parameter (see Notes).
      • equal_nan: bool. Whether to compare NaN’s as equal. If True, NaN’s in a will be considered equal to NaN’s in b in the output array.
    • Returns
      • allclose: bool. Returns True if the two arrays are equal within the given tolerance; False otherwise.
    • See also:
    • DEMO

      參考: 測試兩個numpy數組(接近)是否相等,包括形狀

      import numpy as np
      import math
      
      print(math.pi)
      x = np.array([[3.14159265, -0.1], [-0.1, 0.1]])
      y = np.array([[math.pi, -0.1], [-0.1, 0.1]])
      
      z1 = np.array([[[3.14159265, -0.1], [-0.1, 0.1]],
                    [[3.14159265, -0.1], [-0.1, 0.1]]])
      z2 = np.array([[[math.pi, -0.1], [-0.1, 0.1]],
                    [[math.pi, -0.1], [-0.1, 0.1]]])
      
      print(np.allclose(x,y))
      # Returns true, as expected
      
      print(np.allclose(x,z1))
      # Also returns true, even though matrices are different shapes. Unwanted.
      
      
      a = np.array([[2,2,2],
                    [3,3,3]])
      b = np.array([[2,3,2],
                    [3,3,2]])
      print("=====allclose(atol=0.5)=====")
      print(np.allclose(a, b, atol=0.5))
      print("=====allclose(atol=1.0)=====")
      print(np.allclose(a, b, atol=1.0))
      print("=====equal()=====")
      print(np.equal(a,b))
      
      a = np.array([[2,2,2],
                    [3,3,3.1]])
      print(np.allclose(a, b, atol=1.0))
      print('=====isclose()=====')
      print(np.isclose(a, b, atol=1.0))
      
      3.141592653589793
      True
      True
      =====allclose(atol=0.5)=====
      False
      =====allclose(atol=1.0)=====
      True
      =====equal()=====
      [[ True False  True]
       [ True  True False]]
      False
      =====isclose()=====
      [[ True  True  True]
       [ True  True False]]
      

6.9. [作業1]

  1. 隨機產生一組 30*5 個 0~100 的陣列,模擬成一個班級的某次考試成績(30 人*5 科)。
  2. 輸出此次 5 科考科的全班總分、平均、最高分、最低分、標準差。
  3. 將全班分數以「開根號乘以 10」的方式進行調整。
  4. 如果調整完分數還是不及格,把分數改為39分。
  5. 輸出不及格人數。
  6. 輸出全班分數(至小數點第二位,關於小數點的控制請自行Google關鍵字“numpy.set_printoptions”)。
  • 結果範例
    各科總分: [1341 1522 1548 1411 1627]
    各科平均: [44.7  50.73 51.6  47.03 54.23]
    各科最高分: [94 95 94 98 96]
    各科最低分: [6 0 4 0 0]
    各科標準差: [23.78 27.85 28.07 29.48 27.59]
    全班不及格科目數: 51
    調整後分數:
    
     1: 39.00 86.60 60.00 39.00 62.45
     2: 84.85 90.00 39.00 81.24 39.00
     3: 39.00 86.60 80.62 80.00 39.00
     4: 83.07 39.00 39.00 74.83 77.46
     5: 72.80 39.00 91.10 67.82 39.00
     6: 39.00 70.00 65.57 95.39 97.98
     7: 39.00 90.00 96.95 82.46 88.88
     8: 60.00 92.74 85.44 87.75 83.67
     9: 64.81 39.00 92.20 68.56 39.00
    10: 69.28 68.56 88.88 39.00 85.44
    11: 39.00 39.00 94.87 39.00 76.16
    12: 39.00 39.00 39.00 98.99 78.10
    13: 75.50 88.32 39.00 74.16 66.33
    14: 39.00 39.00 39.00 95.92 79.37
    15: 70.00 39.00 39.00 83.07 73.48
    16: 39.00 39.00 68.56 39.00 95.92
    17: 65.57 39.00 39.00 39.00 74.83
    18: 95.39 63.25 39.00 39.00 88.32
    19: 39.00 95.39 60.83 39.00 74.83
    20: 75.50 39.00 67.08 39.00 96.95
    21: 39.00 39.00 83.67 39.00 77.46
    22: 71.41 60.83 69.28 88.88 77.46
    23: 74.16 97.47 39.00 73.48 93.27
    24: 83.67 84.85 80.00 74.83 39.00
    25: 96.95 95.92 87.18 68.56 39.00
    26: 39.00 83.67 74.83 39.00 39.00
    27: 60.83 77.46 83.67 83.07 91.65
    28: 69.28 64.81 89.44 39.00 39.00
    29: 88.88 83.07 92.74 82.46 95.92
    30: 86.60 74.16 88.32 86.60 39.00
    
    

6.10. 解聯立方程式

Numpy的linalg function可以用來解線性方程組,若目標方程式為\[Ax=b\],其中\[x\]為所求之解,求解語法為:

1: import numpy as np
2: x = np.linalg.solve(A, b)
  1. 範例1

    以如下方程式為例
    \[\begin{cases}2x+y=5\\x+y=3\end{cases}\]
    可將之視為求解\[Ax=b\],其中
    \[A=\begin{pmatrix}2&1\\1&1 \end{pmatrix}\], \[b=\begin{pmatrix} 5\\3 \end{pmatrix} \]

    • solution
      import numpy as np
      A = np.array([[2, 1],
                    [1, 1]])
      b = np.array([5, 3])
      x = np.linalg.solve(A, b)
      print(x)
      print(np.allclose(np.dot(A, x), b)) ##評估兩個向量是否接近(relative tolerance:1e-05, absolute tolerance: 1e-08)
      print(np.dot(A, x)) ##驗證正確性
      
      [2. 1.]
      True
      [5. 3.]
      
  2. 範例2

    求解下列方程式
    \[\begin{cases}3x_0+2x_1+x_2=11\\2x_0+3x_1+x_2=13\\x_0+x_1+4x_2=12 \end{cases}\], 以\[Ax=b\]表示,則
    \[A=\begin{pmatrix}3&2&1\\2&3&1\\1&1&4\end{pmatrix}, x=\begin{pmatrix}x_0\\x_1\\x_2\end{pmatrix}, b=\begin{pmatrix}11\\13\\12\end{pmatrix}\]

    • solution
      import numpy as np
      A = np.array([[3, 2, 1],
                    [2, 3, 1],
                    [1, 1, 4]])
      b = np.array([11, 13, 12])
      x = np.linalg.solve(A, b)
      print(x)
      print(np.allclose(np.dot(A, x), b))
      print(np.dot(A, x))
      
      [1. 3. 2.]
      True
      [11. 13. 12.]
      

6.11. 讀取外部檔案

  1. Google Colab
    • 先將資料檔儲存到Google Drive中

      將要讀取的資料檔(txt, csv)上傳到Google雲端硬碟

    • 要求授權

      在Colab新增一個cell,執行下列程式

      from google.colab import drive
      drive.mount('/content/drive')
      

      選擇“Connected to Google Drive”

    • 測試
      1. 要求授權
      2. 點這裡下載範例資料
      3. 上傳資料檔
      4. 執行以下程式

        !ls drive/MyDrive/
        !cat drive/MyDrive/scores.csv
        

      如果能看到資料的內容,就表示成功了,接下來就能用NumPy來讀取、分析這個資料檔

  2. 用NumPy來讀資料檔
    • load

      在Numpy內會使用.loadtxt或特定的np.genfromtxt來讀取文字檔

      import numpy as np
      myAry = np.loadtxt('drive/MyDrive/scores.csv', delimiter=',')
      print(myAry)
      
      [[109.    87.   100.    86.    50.  ]
       [ 86.    66.    68.97  33.2   55.  ]
       [ 82.    51.    72.87  57.    70.  ]
       [ 90.    81.   100.   100.   100.  ]
       [ 80.    45.    39.66   0.     0.  ]
       [ 83.    36.    74.14  20.    10.  ]
       [ 84.    51.    67.24  25.     0.  ]
       [ 75.    72.    89.66  43.    40.  ]
       [ 76.    63.    96.55  40.     0.  ]
       [ 80.    75.   100.    32.8   50.  ]
       [ 85.    96.    89.48  83.7   30.  ]]
      
    • save

      將處理完的陣列回存成csv檔:

      import numpy as np
      myAry = np.loadtxt('drive/MyDrive/scores.csv', delimiter=',')
      print(myAry)
      np.savetxt('drive/MyDrive/newScores.csv', myAry)
      
  3. 不同格式

    NumPy提供了多種存取陣列內容的檔案操作函式。儲存陣列資料的檔案可以是二進位制格式或者文字格式。二進位制格式的檔案又分為NumPy專用的格式化二進位制型別和無格式型別。27, 28

  4. Bineary Format
    • save(), load()
      import numpy as np
      a = np.arange(0, 12).reshape(3,4)
      print(a)
      np.save('a', a)
      # 讀入
      b = np.load('a.npy')
      print(b)
      
      [[ 0  1  2  3]
       [ 4  5  6  7]
       [ 8  9 10 11]]
      [[ 0  1  2  3]
       [ 4  5  6  7]
       [ 8  9 10 11]]
      
    • savez()

      我們可以儲存多個陣列在一個zip的檔案中,使用np.savez就可以了!

      import numpy as np
      aAry = [1,2,3,4,5,6]
      bAry = [7,8,9,10,11,12]
      #save
      np.savez('ab.npz', a = aAry, b = bAry)
      #load
      myZip = np.load('ab.npz')
      print(myZip['a'])
      print(myZip['b'])
      
      [1 2 3 4 5 6]
      [ 7  8  9 10 11 12]
      
  5. 讀取混合格式的文字資料

    如果資料中某些欄位的資料格式是字串 (string),處理起來相當麻煩,改用 numpy.genfromtxt 會比較簡單。29
    Importing data with genfromtxt
    目標文字檔(csv)如下:

    1: cat cs-scores2.csv
    
    學號,平時成績,打字成績,作業成績,期中考,期末考
    201811101,109.00,87,100.00,86.00,50.00
    201811102,86.00,66,68.97,33.20,55.00
    201811103,82.00,51,72.87,57.00,70.00
    201811104,90.00,81,100.00,100.00,100.00
    201811105,80.00,45,39.66,0.00,0.00
    201811106,83.00,36,74.14,20.00,10.00
    201811107,84.00,51,67.24,25.00,0.00
    201811108,75.00,72,89.66,43.00,40.00
    201811109,76.00,63,96.55,40.00,0.00
    201811110,80.00,75,100.00,32.80,50.00
    201811111,85.00,96,89.48,83.70,30.00
    

    Pyton 3在讀取文字時,dtype應設為U(Unicode),否則會在讀到的字首多出b30

    import numpy as np
    # Python3 is working with Unicode.
    # I had the same issue when using loadtxt with dtype='S'. But using dtype='U as Unicode string in both numpy.loadtxt or numpy.genfromtxt, it will give output without b
    data = np.genfromtxt("cs-scores2.csv", delimiter=',',
                         dtype=[('id', 'U10'), ('cls', float),
                                ('typing', float), ('hw', float),
                                ('mid', float), ('finl', float)],
                         skip_header=1, encoding='UTF-8')
    print(data)
    
    [('201811101', 109., 87., 100.  ,  86. ,  50.)
     ('201811102',  86., 66.,  68.97,  33.2,  55.)
     ('201811103',  82., 51.,  72.87,  57. ,  70.)
     ('201811104',  90., 81., 100.  , 100. , 100.)
     ('201811105',  80., 45.,  39.66,   0. ,   0.)
     ('201811106',  83., 36.,  74.14,  20. ,  10.)
     ('201811107',  84., 51.,  67.24,  25. ,   0.)
     ('201811108',  75., 72.,  89.66,  43. ,  40.)
     ('201811109',  76., 63.,  96.55,  40. ,   0.)
     ('201811110',  80., 75., 100.  ,  32.8,  50.)
     ('201811111',  85., 96.,  89.48,  83.7,  30.)]
    
  6. 讀取一個欄位資料

    在 numpy.genfromtxt() 中, 使用 usecols 參數指定取出的欄位編號, 即可取出特定欄位

    import numpy as np
    
    classScore = np.genfromtxt("cs-scores2.csv", delimiter=',', skip_header=1,
                         dtype=float, usecols=(2,), unpack=True, encoding='UTF-8')
    print(classScore)
    
    [87. 66. 51. 81. 45. 36. 51. 72. 63. 75. 96.]
    

6.12. 基礎練習

  1. 創建一個長度為10的零向量,並把第五個值賦值為1
    • example
      [0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]
      
  2. 創建一個5x5的矩陣,值域為1,3,5,7…,49
    • example
      [[ 1  3  5  7  9]
       [11 13 15 17 19]
       [21 23 25 27 29]
       [31 33 35 37 39]
       [41 43 45 47 49]]
      
  3. 創建一個3x3x3的隨機數組
    • example
      [[[0.65641795 0.9853537  0.64412663]
        [0.52595413 0.17452888 0.44484008]
        [0.69218998 0.37001109 0.94711544]]
      
       [[0.80803134 0.93661621 0.61489936]
        [0.19635856 0.98132637 0.36713757]
        [0.89105201 0.31710182 0.12375642]]
      
       [[0.42944896 0.24734458 0.97763098]
        [0.96900319 0.64789449 0.42795362]
        [0.72306476 0.07604109 0.13012237]]]
      
  4. 創建一個8x8的國際象棋棋盤矩陣(黑塊為0,白塊為1)
    • example
      [[0 1 0 1 0 1 0 1]
       [1 0 1 0 1 0 1 0]
       [0 1 0 1 0 1 0 1]
       [1 0 1 0 1 0 1 0]
       [0 1 0 1 0 1 0 1]
       [1 0 1 0 1 0 1 0]
       [0 1 0 1 0 1 0 1]
       [1 0 1 0 1 0 1 0]]
      
  5. 對5x5的隨機矩陣進行歸一化(Normalization)

    (提示: (x - min) / (max - min))

    • example
      [[93.91707     7.2497428  80.03357931 67.36581629 11.58313519]
       [12.57003038 38.52508111 83.86899353 27.63880275 25.8237963 ]
       [18.68409281 64.40845991 41.08228881 62.87822465 52.23296308]
       [90.53354354 78.60844019 46.98888235 29.757448   38.46802081]
       [71.53666934 63.90804362 64.40403658 29.34575551 82.74560132]]
      [[1.         0.         0.8398071  0.69364172 0.0500003 ]
       [0.06138747 0.36086654 0.88406154 0.23525659 0.21431437]
       [0.1319338  0.65951863 0.39037256 0.64186221 0.5190332 ]
       [0.96095961 0.82336331 0.45852504 0.25970231 0.36020815]
       [0.74176658 0.65374464 0.65946759 0.25495205 0.87109942]]
      
  6. 如何找出兩個數組公共的元素?

    (提示: np.intersect1d)

    • example
      [3 4 2 8 0 3 5 4 7 9]
      [9 3 4 6 5 6 3 0 5 6]
      [0 3 4 5 9]
      
  7. 如何用一個生成10個整數的函數來構建數組

    (提示: np.fromiter)

    • example
      [0. 1. 2. 3. 4. 5. 6. 7. 8. 9.]
      
  8. 對一個小數組進行求和有沒有辦法比np.sum更快?

    (提示: np.add.reduce)

    • example
      45
      45
      
  9. 如何判斷兩隨機數組相等

    (提示: np.allclose, np.array_equal)

    • example
      =====A=====
      [1 1 0 0 0]
      =====B=====
      [1 1 0 0 0]
      True
      True
      
  10. 創建一個大小為10的隨機向量並且將該向量中最大的值替換為0

    (提示: argmax)

    • example
      [0.509 0.491 0.888 0.604 0.409 0.136 0.523 0.068 0.307 0.869]
      [0.509 0.491 0.    0.604 0.409 0.136 0.523 0.068 0.307 0.869]
      
  11. 思考形狀為(100, 2)的隨機向量,求出點與點之間的距離

    (提示: np.atleast_2d, T, np.sqrt)

    • example
      [[0.24 0.44]
       [0.87 0.84]
       [0.87 0.63]
       [0.56 0.11]
       [0.67 0.47]]
      [[0.   0.74 0.66 0.46 0.43]
       [0.74 0.   0.21 0.79 0.42]
       [0.66 0.21 0.   0.61 0.26]
       [0.46 0.79 0.61 0.   0.37]
       [0.43 0.42 0.26 0.37 0.  ]]
      [[0.   0.45 0.29 0.77 0.78]
       [0.45 0.   0.68 1.04 0.76]
       [0.29 0.68 0.   0.93 1.05]
       [0.77 1.04 0.93 0.   0.58]
       [0.78 0.76 1.05 0.58 0.  ]]
      
  12. 如何在二維數組的隨機位置放置p個元素?(提示: np.put, np.random.choice)
    • example
      [[0. 0. 0. 0. 1.]
       [0. 0. 1. 0. 0.]
       [0. 0. 1. 0. 0.]
       [0. 0. 0. 0. 0.]
       [0. 0. 0. 0. 0.]]
      
  13. 如何對數組通過第n列進行排序? (提示: argsort)
    • example
      [[6 1 5]
       [8 7 9]
       [9 5 1]]
      [[9 5 1]
       [6 1 5]
       [8 7 9]]
      
  14. 從數組中找出與給定值最接近的值(提示: np.abs, argmin, flat)
    • example
      [0.10888426 0.00635016 0.80979507 0.99058779 0.37085609 0.0438693
       0.33289479 0.02195115 0.74203634 0.48734223]
      =====最接近 0.5 的陣列值及其位置=====
      (array([9]),) :  0.487342230530487
      
  15. 創建一個具有name屬性的數組類(提示: class method)
    • example
      range_10
      
  16. 考慮一個維度(5,5,3)的數組,如何將其與一個(5,5)的數組相乘?(提示: array[:, :, None])
    • example
      [[[2. 2. 2.]
        [2. 2. 2.]
        [2. 2. 2.]
        [2. 2. 2.]
        [2. 2. 2.]]
      
       [[2. 2. 2.]
        [2. 2. 2.]
        [2. 2. 2.]
        [2. 2. 2.]
        [2. 2. 2.]]
      
       [[2. 2. 2.]
        [2. 2. 2.]
        [2. 2. 2.]
        [2. 2. 2.]
        [2. 2. 2.]]
      
       [[2. 2. 2.]
        [2. 2. 2.]
        [2. 2. 2.]
        [2. 2. 2.]
        [2. 2. 2.]]
      
       [[2. 2. 2.]
        [2. 2. 2.]
        [2. 2. 2.]
        [2. 2. 2.]
        [2. 2. 2.]]]
      
  17. 如何對一個數組中任意兩行做交換?(提示: array[[]] = array[[]])
    • example
      [[ 0  1  2  3  4]
       [ 5  6  7  8  9]
       [10 11 12 13 14]
       [15 16 17 18 19]
       [20 21 22 23 24]]
      [[ 5  6  7  8  9]
       [ 0  1  2  3  4]
       [10 11 12 13 14]
       [15 16 17 18 19]
       [20 21 22 23 24]]
      
  18. 思考描述10個三角形(共享頂點)的一組10個三元組,找到組成所有三角形的唯一線段集(提示: repeat, np.roll, np.sort, view, np.unique)
    • example
      [( 1, 31) ( 1, 77) ( 2, 23) ( 2, 33) ( 8, 43) ( 8, 88) ( 9, 55) ( 9, 93)
       (17, 61) (17, 65) (17, 70) (17, 74) (21, 38) (21, 67) (23, 33) (31, 77)
       (36, 58) (36, 63) (38, 67) (43, 88) (52, 65) (52, 92) (55, 93) (58, 63)
       (61, 70) (65, 74) (65, 92) (90, 91) (90, 98) (91, 98)]
      
  19. 如何找出數組中出現頻率最高的值?(提示: np.bincount, argmax)
  20. 從一個5x5的矩陣中提取出連續的3x3區塊**(提示: stride_tricks.as_strided)
    • example
      [[2 5 1 4 6]
       [2 6 7 2 4]
       [3 7 7 4 6]
       [8 1 4 0 3]
       [1 1 7 7 4]]
      [[[[2 5 1]
         [2 6 7]
         [3 7 7]]
      
        [[5 1 4]
         [6 7 2]
         [7 7 4]]
      
        [[1 4 6]
         [7 2 4]
         [7 4 6]]]
      
      
       [[[2 6 7]
         [3 7 7]
         [8 1 4]]
      
        [[6 7 2]
         [7 7 4]
         [1 4 0]]
      
        [[7 2 4]
         [7 4 6]
         [4 0 3]]]
      
      
       [[[3 7 7]
         [8 1 4]
         [1 1 7]]
      
        [[7 7 4]
         [1 4 0]
         [1 7 7]]
      
        [[7 4 6]
         [4 0 3]
         [7 7 4]]]]
      
  21. 對於一個16x16的數組,如何得到一個區域的和(區域大小為4x4)? (提示: np.add.reduceat)
  22. 如何找到一個數組的第n個最大值?(提示: np.argsort | np.argpartition)

6.13. 資料的正規化(Normalization)及標準化(Standardization)

當我們在比較分析兩組數據資料時,可能會遭遇因單位的不同(例如:身高與體重),或數字大小的代表性不同(例如:粉專1萬人與滿足感0.8),造成各自變化的程度不一,進而影響統計分析的結果;為解決此類的問題,我們可利用資料的正規化(Normalization)與標準化(Standardization),藉由將原始資料轉換成無量綱(Dimensionless)的純量後,來進行數據的比較及分析。31

  1. Normalization

    原始資料的數據按比例縮放於 [0, 1] 區間中,且不改變其原本分佈。舉例來說,若我們現有兩組數據資料,分別表示 500 項商品的銷售量 Sample 1 及銷售額 Sample 2,如下圖所示,很明顯地,此兩組資料的單位不同,且數字上有著懸殊的差異,分別透過資料正規化後,兩組資料將同時轉換成純量縮放於 [0,1] 區間中,如下右圖所示;這樣的資料轉換,能排除資料單位的限制,提供我們一個相同的基準來進行後續比較分析。

    Norm.jpg

    Figure 6: Normalization

    • 實作

      最小值最大值正規化的用意,是將資料等比例縮放到 [0, 1] 區間中,可利用下列公式進行轉換:
      \[X_{nom} = \frac{X-X_{min}}{X_{max}-X{min}} \in [0,1]\]
      其中 Xmax 與 Xmin 分別為資料中的最小值與最大值。此種方法有一點需我們特別注意,即若原始資料有新的數據加入,有可能導致最小值 Xmin 及最大值 Xmax 的改變,則這時候我們需再重新定義公式中的 Xmin 及 Xmax。另外,若將轉換公式修改成下列:
      \[X_{nom} = \frac{X-\mu}{X_{max}-X{min}} \in [-1,1]\]
      其中\[\mu\]為資料的平均值,則資料將縮放到 [-1, 1] 區間中且平均值 = 0,我們稱這為平均值正規化(Mean Normalization)。

      在實務操作最小值最大值正規化時,我們可使用 Scikit-learn 套件的MinMaxScaler 物件執行。

  2. Standardization

    會將所有特徵數據縮放成平均為0、平方差為1。資料的標準化(Standardization)可運用在機器學習演算法中,它能帶給模型下面兩個好處:

    • 提升模型的收斂速度
      在建構機器學習模型時,我們會利用梯度下降法(Gradient Descent)來計算成本函數(Cost Function)的最佳解;假設我們現有兩個特徵值 x1 in [0,1] 與 x2 in [0,10000],則在 x1-x2 平面上成本函數的等高線會呈窄長型,導致需較多的迭代步驟,另外也可能導致無法收斂的情況發生。因此,若將資料標準化,則能減少梯度下降法的收斂時間。
    • 提高模型的精準度
      將特徵值 x1 及 x2 餵入一些需計算樣本彼此的距離(例如:歐氏距離)分類器演算法中,則 x2 的影響很可能將遠大於 x1,若實際上 x1 的指標意義及重要性高於 x2,這將導致我們分析的結果失真。因此,資料的標準化是有必要的,可讓每個特徵值對結果做出相近程度的貢獻。
    • 實作

      \[Z = \frac{X-\mu}{\delta} \sim N(0, 1)\]
      經 Z分數標準化後,資料將符合標準常態分佈(Standard Normal Distribution),轉換後的平均值=0、標準差=1,且用標準分數或稱 Z分數(Z-Score)來作為單位。Z分數標準化適用於分佈大致對稱的資料,因為在非常不對稱的分佈中,標準差的意義並不明確,此時若標準化資料,可能會對結果做出錯誤的解讀,另外,當我們未知資料的最大值與最小值,或存在超出觀察範圍的離群值時,可透過 Z分數標準化來降低離群值對整個模型的影響。

      在實務操作時,我們可先分別計算資料的平均值及標準差,再代入上述公式完成標準化,另一種方法,我們可使用Scikit-learn 套件的preprocessing 模組來執行資料標準化。

6.14. [作業2]

  • 模擬一個37人/7科的全班月考成績(隨機生成)
  • 將所有原始分數轉換為Z分數

7. Matplotlib

7.1. Matplotlib 簡介

  • 官網: https://matplotlib.org/3.2.1/index.html#
  • Matplotlib 是利用 Python 所實作的繪圖套件,其中包含兩個最重要的模組: pylab和pyplot。
  • pylab 已經幾乎實作了在學術界最常用的套件 — Matlab 所支援的繪圖功能,或者可以說 pylab 其實就是 Matlab 的 Python 版本;
  • pyplot 是把 pylab 再加上 Numpy,讓使用者在使用 pyplot 時,可以直接呼叫 Numpy 的函式做計算後再以圖型的方式呈現。32
  • 為 Python 最多人使用的 2D 繪圖工具
  • 優點:圖形美觀、類型多、相容於 Matlab
  • 官方社群網站 https://matplotlib.org/
  • 維基百科 https://zh.wikipedia.org/wiki/Matplotlib

7.2. Matplotlib 基本語法

  1. 基本語法
    • import matplotlib.pyplot as plt
    • plt.plot()函式為 matplotlib.pyplot 模組畫線條方法,其語法如下


        plt.plot( [x座標資料,] y座標資料 [, 參數1, 參數2, ...] )
      
    • plt.plot()官網說明
  2. 範例:簡單的 sin 圖形
    • np.sin( )函式為 Numpy 模組求正弦值
    • plt.show( )函式用來顯示圖形
    • 請到官網看看除了sin()還有哪些function可以玩,畫出幾個函式曲線
    import matplotlib.pyplot as plt
    import numpy as np
    
    x = np.arange(-3, 3, 0.1)
    plt.clf()
    plt.plot(x, np.sin(x))
    
    # 若要存圖,要先存檔再顯示 # for jupyter
    plt.savefig('images/SimpleSin.png', dpi=300)
    # plt.show()   # for jupyter
    
    
    

    simpleSin.png

    Figure 7: 簡單的 sin 圖形

  3. plot 官方語法

    plot()函式控制輸出主要有以下兩類參數:

    • fmt 字串
    • kwarg 參數

    基本語法如下:

    • plot([x], y, [fmt], *, data=None, **kwargs)
    • plot([x], y, [fmt], [x2], y2, [fmt2], …, **kwargs)

7.3. fmt控制參數

  • 可選引數 fmt 是定義基本格式 (如顏色、標記和 linestyle) 的簡便方法
  • plot(): fmt 字串
字元 顏色 字元 標記 字元 線條
’b’ ’.’ ’–’(兩個-) 虛線
’g’ ’o’ 圓圈 ’-’ 實線
’r’ ’v’ 三角形(下) ’-.’ -.-.-.-.
’c’ ’^’ 三角形(上)    
’m’ 洋紅 ’<’ 三角形(左)    
’y’ ’>’ 三角形(右)    
’k’ ’s’ 正方形    
’w’ ’p’ 五邊形    
    ’*’ *    
    ’+’ +    
    ’x’ x    
    ’d’ 鑽石    
  1. Demo #1
    import matplotlib.pyplot as plt
    import numpy as np
    
    x = np.arange(-3, 3, 0.1)
    plt.clf()
    plt.plot(x, np.sin(x), 'r+')
    # for web-based
    # plt.show()
    
    # saving figure
    plt.savefig('images/SimpleSin2.png', dpi=300)
    
    

    SimpleSin2.png

    Figure 8: 簡單的 sin 圖形

  2. Demo #2
    import matplotlib.pyplot as plt
    import numpy as np
    
    x = np.arange(-3, 3, 0.1)
    plt.clf()
    plt.plot(x, np.cos(x), 'c-.')
    # for web-based
    # plt.show()
    
    # saving figure
    plt.savefig('images/SimpleCos.png', dpi=300)
    
    

    SimpleCos.png

    Figure 9: 簡單的 cos圖形

  3. Demo: 多組資料
    import matplotlib.pyplot as plt
    import numpy as np
    
    x1 = np.arange(-3, +3, 0.1)
    y1 = np.sin(x1)
    y2 = np.cos(x1)
    
    #plt.plot(x1, y1, 'r^--', x1, y2, 'go-')
    plt.plot(x1, y1, 'r^--', x1, y2, 'go-')
    plt.savefig('mline.png', dpi=300)
    
    

    mline.png

    Figure 10: 多組圖形

7.4. kwgarg 參數

  1. kwarg 為 Line2D 屬性:color, linestyle, marker, label, linewidth, ….
    • kwargs 用於指定諸如線條標籤 (用於自動圖例)、線寬、抗鋸齒、標記面顏色等屬性
    • 若 fmt 和 kwarg 設定衝突時,以 kwarg 為主
  2. color
    • 單字,如 g:color = ’lime’
    • 字母,如 g:color = ’k’
    • 色碼,如 g:color = ’#FF0000’
    • RGB 值(0~g1 之間),如:color = (1, 0, 0)
    字元 顏色
    ’b’ 藍色
    ’g’ 綠色
    ’r’
    ’c’ 青色
    ’m’ 品紅
    ’y’ 黃色
    ’k’
    ’w’ 白色
  3. marker
    字元 描述
    ’.’ 點標記
    ’,’ 畫素標記
    ’o’ 圓圈標記
    ’v’ triangle_down 標記
    ’^’ triangle_up 標記
    ’<’ triangle_left 標記
    ’>’ triangle_right 標記
    ’1’ tri_down 標記
    ’2’ tri_up 標記
    ’3’ tri_left 標記
    ’4’ tri_right 標記
    ’s’ 方形標記
    ’p’ 五角大樓標記
    ’*’ 星形標記
    ’h’ hexagon1 標記
    ’H’ hexagon2 標記
    ’+’ 加號標記
    ’x’ x 標記
    ’D’ 鑽石標記
    ’d’ thin_diamond 標記
    ’|’ 圴標記
    ’_’ 修身標記
    • linestyle
      字元 描述
      ’-’ 實線樣式
      ’–’ 虛線樣式
      ’-.’ 破折號-點線樣式
      ’:’ 虛線樣式
    • label
      • fg 呈現線條標籤,如 label = ’y = x^2’
      • 需搭配 pglt.legend()函式方能呈現 label
  4. 其他參數
    • x / y 座標範圍:plt.xlim(起始值, 終止值) / plt.ylim(起, 止)
    • 圖表標題:plt.title(字串)
    • x / y 座標標題:plt.xlabel(字串) / plt.ylabel(字串)
    • 顯示 kwarg 參數裡的 label:plt.legend()
  5. kwarg 示範
    import matplotlib.pyplot as plt
    import numpy as np
    
    x = np.arange(-3, 3, 0.5)
    plt.clf()
    plt.plot(x, np.cos(x), color='c', linestyle='--', marker='p')
    plt.xlim(-4, 4)
    plt.xlabel('This is x label')
    plt.title("This is Title", fontsize=16)
    
    plt.savefig('images/kwarg.png', dpi=300)
    
    

    kwarg.png

    Figure 11: kwarg參數控制

7.5. 在Spyder中使用中文

  • 解決方案
# 解決中文問題
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS'] # 步驟一(替換系統中的字型,這裡用的是Mac OSX系統)
plt.rcParams['axes.unicode_minus'] = False  # 步驟二(解決座標軸負數的負號顯示問題)
  • 實際示例

marriage.png

Figure 12: 簡單的折線圖及圖例

7.6. 在colab中使用中文

  1. 在ipynb最上方先加入下列cell
# Colab 進行matplotlib繪圖時顯示繁體中文
# 下載台北思源黑體並命名taipei_sans_tc_beta.ttf,移至指定路徑
!wget -O TaipeiSansTCBeta-Regular.ttf https://drive.google.com/uc?id=1eGAsTN1HBpJAkeVM57_C7ccp7hbgSz3_&export=download

import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib.font_manager import fontManager

# 改style要在改font之前
# plt.style.use('seaborn')

fontManager.addfont('TaipeiSansTCBeta-Regular.ttf')
mpl.rc('font', family='Taipei Sans TC Beta')

7.7. Legend

import matplotlib.pyplot as plt
import numpy as np

x = np.arange(-3, 3, 0.1)
plt.clf()
plt.plot(x, np.sin(x))
plt.plot(x, np.cos(x))
#加入圖例
plt.legend(["sin", "cos"], loc="upper left")


# 若要存圖,要先存檔再顯示 # for jupyter
plt.savefig('images/sincoslegend.png', dpi=300)

sincoslegend.png

Figure 13: Caption

7.8. Line Chart

  1. Demo #1
    import matplotlib.pyplot as plt
    import numpy as np
    
    x1 = [1, 2, 3, 4, 5, 6]
    y1 = [1, 2, 3, 4, 5, 6]
    x2 = np.arange(0, 3, 0.3)
    x3 = np.arange(8).reshape(2, 4)
    print(x3)
    print(x3+3)
    plt.plot(y1, 'c-p', x2, x2**2, 'ms:', x3, x3+3, '-*')
    plt.savefig('line2.png', dpi=300)
    
    [[0 1 2 3]
     [4 5 6 7]]
    [[ 3  4  5  6]
     [ 7  8  9 10]]
    

    line2.png

    Figure 14: 簡單的折線圖形 2

  2. Demo #2
    import matplotlib.pyplot as plt
    import numpy as np
    
    def angdeg(x):
        return 1.2*x+3
    
    x = np.arange(0, 21, 1)
    plt.rcParams['font.sans-serif'] = ['Arial Unicode MS'] # 步驟一(替換系統中的字型,這裡用的是Mac OSX系統)
    plt.rcParams['axes.unicode_minus'] = False  # 步驟二(解決座標軸負數的負號顯示問題)
    ax = plt.figure().gca()
    plt.plot(x, angdeg(x), color='r', linestyle='--', linewidth=3)
    plt.xlabel('本月闖禍次數', fontsize=16)
    plt.ylabel('媽媽生氣程度', fontsize=16)
    plt.xlim(0,21)
    ax.set_xticks(x)
    plt.savefig('images/angrymam-1.png', dpi=300)
    

    angrymam-1.png

    Figure 15: Angry Mam

  3. Demo #3
    import matplotlib.pyplot as plt
    import numpy as np
    
    def angdeg(x):
        return 500+(x**4)/100
    
    x = np.arange(0, 21, 1)
    #plt.cla()
    plt.rcParams['font.sans-serif'] = ['Arial Unicode MS'] # 步驟一(替換系統中的字型,這裡用的是Mac OSX系統)
    plt.rcParams['axes.unicode_minus'] = False  # 步驟二(解決座標軸負數的負號顯示問題)
    
    ax = plt.figure().gca()
    plt.plot(x, angdeg(x), color='r', linestyle='--', linewidth=3)
    
    plt.xlabel('本月闖禍次數', fontsize=16)
    plt.ylabel('媽媽生氣程度', fontsize=16)
    plt.xlim(0,21)
    plt.ylim(0,2201)
    ax.set_xticks(x)
    plt.savefig('images/angrymam-2.png', dpi=300)
    

    angrymam-2.png

    Figure 16: Angry Mam

  4. Demo #4
    import matplotlib.pyplot as plt
    import numpy as np
    import pandas as pd
    
    def angdeg(x):
        return x**4
    def l2c(x):
        if x == True:
            return 'r'
        else:
            return 'g'
    
    np.random.seed(9527)
    x = np.random.randint(21, size=20)
    y = np.random.randint(21, size=20)
    z = x + y < 20
    colors = list(map(lambda x: 'g' if x == True else 'r', z))
    #colors = list(map(l2c, z))
    labels = list(map(lambda x: '挨揍' if x == True else '平安', z))
    plt.rcParams['font.sans-serif'] = ['Arial Unicode MS'] # 步驟一(替換系統中的字型,這裡用的是Mac OSX系統)
    plt.rcParams['axes.unicode_minus'] = False  # 步驟二(解決座標軸負數的負號顯示問題)
    
    ax = plt.figure().gca()
    
    #plt.scatter(x, y, c = colors)
    rc = [i for i in range(len(colors)) if colors[i] == 'r']
    gc = [i for i in range(len(colors)) if colors[i] == 'g']
    
    plt.scatter(x[rc], y[rc], c = 'r')
    plt.scatter(x[gc], y[gc], c = 'g')
    plt.xlabel('因闖禍受傷程度', fontsize=16)
    plt.ylabel('因闖禍導致損失金額', fontsize=16)
    ax.set_xticks(range(21))
    ax.set_yticks(range(21))
    plt.legend(['挨揍','平安'])
    plt.plot(x, 20-x, linewidth=3)
    plt.savefig('images/angrymam-3.png', dpi=300)
    

    angrymam-3.png

    Figure 17: Angry Mam

  5. Demo #5
    import matplotlib.pyplot as plt
    import numpy as np
    import pandas as pd
    
    def angdeg(x):
        return x**4
    def l2c(x):
        if x == True:
            return 'r'
        else:
            return 'g'
    def mycurv(x):
        return 5 + (2 ** (19-float(x)))/6000
    
    np.random.seed(9527)
    x = np.random.randint(21, size=20)
    y = np.random.randint(21, size=20)
    y1 = list(map(mycurv, x))
    print('y:',y)
    print('y1:',y1)
    z = y > y1
    print('z:', z)
    colors = list(map(lambda x: 'g' if x == True else 'r', z))
    colors = list(map(l2c, z))
    #labels = list(map(lambda x: '挨揍' if x == True else '平安', z))
    plt.rcParams['font.sans-serif'] = ['Arial Unicode MS'] # 步驟一(替換系統中的字型,這裡用的是Mac OSX系統)
    plt.rcParams['axes.unicode_minus'] = False
    # 步驟二(解決座標軸負數的負號顯示問題)
    ax = plt.figure().gca()
    #
    ##plt.scatter(x, y, c = colors)
    rc = [i for i in range(len(colors)) if colors[i] == 'r']
    gc = [i for i in range(len(colors)) if colors[i] == 'g']
    
    plt.scatter(x[rc], y[rc], c = 'r')
    plt.scatter(x[gc], y[gc], c = 'g')
    plt.xlabel('因闖禍受傷程度', fontsize=16)
    plt.ylabel('因闖禍導致金錢損失', fontsize=16)
    ax.set_xticks(x)
    plt.legend(['挨揍','平安'])
    x1=np.arange(21,0,-1, dtype=float)
    print(x1)
    
    plt.plot(x1, 5 + (2 ** (19-x1))/6000, linewidth=3)
    plt.xlim(0,21)
    plt.ylim(0,21)
    ax.set_xticks(range(21))
    ax.set_yticks(range(21))
    plt.savefig('images/angrymam-4.png', dpi=300)
    
    y: [15  5  1  1 16 12  6  3 14 15  6 17  2 12  0 11  6  7  9 13]
    y1: [48.690666666666665, 5.341333333333333, 5.001333333333333, 5.001333333333333, 5.021333333333334, 5.341333333333333, 5.001333333333333, 48.690666666666665, 5.000083333333333, 5.042666666666666, 5.1706666666666665, 26.845333333333333, 5.001333333333333, 5.010666666666666, 5.021333333333334, 92.38133333333333, 48.690666666666665, 7.730666666666666, 6.365333333333333, 26.845333333333333]
    z: [False False False False  True  True  True False  True  True  True False
     False  True False False False False  True False]
    [21. 20. 19. 18. 17. 16. 15. 14. 13. 12. 11. 10.  9.  8.  7.  6.  5.  4.
      3.  2.  1.]
    

    angrymam-4.png

    Figure 18: Angry Mam

  6. 練習1: 繪製折線圖
    1. 利用 list 繪製

      • x1 = [1, 2, 3, 4, 5, 6]

      • y1 = [1, 2, 3, 4, 5, 6]

    2. 執行 plt.plot(y1) 和 plt.plot(x1, y1) 有何差別??
    3. 利用 numpy 繪製

      • x2 = np.arange(0, 3, 0.01)

    4. 執行 plt.plot(x2, x2**2)看看畫出什麼圖形??
  7. 課堂作業: 圖表美化
    1. 7.8.6的圖表加上標記、線條標籤,換線條顏色、線條樣式
    2. 請將下列資料繪成折線圖(男女折線不同顏色、樣式,加標記)

      初婚年齡 2006 2011 2014 2015 2016
      30.7 31.8 32.1 32.2 32.4
      27.8 29.4 29.9 30.0 30.0
      • 圖表標題: Age of first marriage

      • 座標軸標題: x ⇒ Year、y ⇒ Age

      • 線條標籤: 男 ⇒ Male、女 ⇒ Female
      • 結果示例

    marriage.png

7.9. Bar chart

  • 語法
plt.bar( x座標資料, y座標資料 [, 參數1, 參數2, ...] )
plt.barh( x座標資料, y座標資料 [, 參數1, 參數2, ...] )
  • DEMO
import matplotlib.pyplot as plt
import numpy as np

objects = ('Python', 'C++', 'Java', 'Perl', 'Scala', 'Lisp')
y_pos = np.arange(len(objects))
performance = [10,8,6,4,2,1]

plt.barh(y_pos, performance, align='center', color = ['b','g','r','c','m','y'], alpha=0.5)
plt.yticks(y_pos, objects)
plt.xlabel('Usage')
plt.title('Programming language usage')
#plt.show()
plt.savefig('barChart.png', dpi=300)

barChart.png

Figure 19: barChart Demo

7.10. Pie chart

  1. 語法:


    plt.pie( 比例列表 [, 參數 1, 參數 2, …] )

  2. 參數:
    • colors:各子圖顏色,多以 list 表示
    • labels:各子圖標籤,多以 list 表示
    • explode:各子圖分離突出比例,0.1 代表分離 10%,多以 list 表示
    • autopct:顯示各子圖比例值,格式為%x.y%%
    • startangle:繪製起始角度,預設為 0 (與三角函數角度相同)
    • 若要以正圓形繪製,請再加上 plt.axis(’equal’)
    • legend location
    Location String Location Code
    ’best’ 0
    ’upper right’ 1
    ’upper left’ 2
    ’lower left’ 3
    ’lower right’ 4
    ’right’ 5
    ’center left’ 6
    ’center right’ 7
    ’lower center’ 8
    ’upper center’ 9
    ’center’ 10
  3. 範例
    import matplotlib.pyplot as plt
    import numpy as np
    
    parts = [35.35, 23, 26.65, 15]
    labels = ['Harrison', 'Vanessa', 'James', 'Ruby']
    colors = ['red', 'lightblue', 'purple', 'yellow']
    explodes = [0.1, 0, 0, 0.1]
    plt.pie(parts, colors = colors, labels = labels, explode = explodes, autopct = '%3.2f%%')
    plt.axis('equal')
    plt.legend(loc='upper left')
    
    #plt.show()
    plt.savefig('simplePie.png', dpi=300)
    

    simplePie.png

    Figure 20: 簡單的 pie chart

7.11. 其它圖表

7.12. 文字註解: plt.text()

  1. 語法
    • plt.text( x相對座標, y相對座標 , 文字字串 [, 其它參數] )
    • 參考資料
  2. 範例
    import matplotlib.pyplot as plt
    
    x = [1, 2, 3, 4, 5, 6, 7, 8]
    y = [1, 4, 9, 16, 25, 36, 49, 64]
    plt.plot(x, y, 'r--')
    for x, y in zip(x, y):
        plt.text(x-0.2, y+0.6, '(%d, %d)' %(x, y))
    #plt.show()
    plt.savefig('simpleText.png', dpi = 300)
    

    simpleText.png

    Figure 21: 簡單的文字註解

7.13. 子圖表: plt.subplot()

matplotlib下, 一個 Figure 對象可以包含多個子圖(Axes), 可以使用 subplot() 快速繪製, 其調用形式如下 :

  1. subplot語法

    subplot(numRows, numCols, plotNum)

    • 圖表的整個繪圖區域被分成 numRows 行和 numCols 列
    • 然後按照從左到右,從上到下的順序對每個子區域進行編號,左上的子區域的編號為1
    • plotNum 參數指定創建的 Axes 對象所在的區域
    • 如果 numRows, numCols 和 plotNum 這三個數都小於 10 的話, 可以把它們縮寫為一個整數, 例如 subplot(323) 和 subplot(3,2,3) 是相同的.
  2. subplots_adjust語法
    • subplots_adjust(left=None, bottom=None, right=None, top=None, wspace=None, hspace=None)
    • left = 0.125 # 子圖(subplot)距畫板(figure)左邊的距離
    • right = 0.9 # 右邊
    • bottom = 0.1 # 底部
    • top = 0.9 # 頂部
    • wspace = 0.2 # 子圖水平間距
    • hspace = 0.2 # 子圖垂直間距
  3. 範例33
    import numpy as np
    import matplotlib.pyplot as plt
    
    from matplotlib.ticker import NullFormatter  # useful for `logit` scale
    
    # Fixing random state for reproducibility
    np.random.seed(19680801)
    
    # make up some data in the interval ]0, 1[
    y = np.random.normal(loc=0.5, scale=0.4, size=1000)
    y = y[(y > 0) & (y < 1)]
    y.sort()
    x = np.arange(len(y))
    
    # plot with various axes scales
    plt.figure()
    
    # linear
    plt.subplot(221)
    plt.plot(x, y)
    plt.yscale('linear')
    plt.title('linear')
    plt.grid(True)
    
    
    # log
    plt.subplot(222)
    plt.plot(x, y)
    plt.yscale('log')
    plt.title('log')
    plt.grid(True)
    
    
    # symmetric log
    plt.subplot(223)
    plt.plot(x, y - y.mean())
    plt.yscale('symlog', linthreshy=0.01)
    plt.title('symlog')
    plt.grid(True)
    
    # logit
    plt.subplot(224)
    plt.plot(x, y)
    plt.yscale('logit')
    plt.title('logit')
    plt.grid(True)
    # Format the minor tick labels of the y-axis into empty strings with
    # `NullFormatter`, to avoid cumbering the axis with too many labels.
    plt.gca().yaxis.set_minor_formatter(NullFormatter())
    
    # Adjust the subplot layout, because the logit one may take more space
    # than usual, due to y-tick labels like "1 - 10^{-3}"
    plt.subplots_adjust(top=0.92, bottom=0.08, left=0.10, right=0.95, hspace=0.25, wspace=0.35)
    
    plt.savefig('simpleSubplot.png', dpi = 300)
    

    simpleSubplot.png

    Figure 22: 簡單的子圖表

  4. 進階設定

7.14. 實作練習

  1. Line: 輸入 a, b, c,畫出曲線圖:y=\(ax^2+bx+c\),參考結果如下:

    eqcurve.png

    Figure 23: 方程式曲線圖

  2. Bar: 將前一章的練習題所產生 30*5 的二維隨機陣列,將五科科目的平均製成長條圖,此五科科目設定為:國文、英文、數學、理化、史地。參考結果如下:

    scobars.png

    Figure 24: 分數長條圖

  3. Scatter: 找出國文與英文兩科成績,畫出兩科成績的散佈圖(Scatter),觀察學生在這兩科的得分是否有正相關。參考結果如下:

    scosct.png

    Figure 25: 兩科成績的關係圖

  4. 請自行上網搜尋學習資源下載這個檔案,畫出以下圖形(提示: Google關鍵字 matplotlib, boxplot)

    npbox.png

    Figure 26: Caption

8. Pandas

8.2. Excel/CSV: Pandas 模組

  1. CSV
    • 為純文字檔,以逗號分隔值(Comma-Separated Values,CSV,有時也稱為字元分隔值,因為分隔字元也可以不是逗號)。
    • 純文字意味著該檔案是一個字元序列,不含必須像二進位制數字那樣被解讀的資料。
    • CSV 檔案由任意數目的記錄組成,記錄間以某種換行符分隔;每條記錄由欄位組成,欄位間的分隔符是其它字元或字串,最常見的是逗號。
  2. Pandas
    • Pandas 為建構於 NumPy 之上的套件,為 python 的一個數據分析模組,適合處理表格資料或異質資料。
    • 於 2009 年底開源出來,提供高效能、簡易使用的資料格式(Data Frame,為 R 語言主要資料格式),可以讓使用者可以快速操作及分析資料。
    • Pandas 強化了資料處理的方便性也能與處理網頁資料與資料庫資料等,有點類似於Office 的 Excel,能更加方便的進行運算、分析等34
    • Pandas 主要特色有35
      1. 在異質數據的讀取、轉換和處理上,都讓分析人員更容易處理,例如:從列欄試算表中找到想要的值。
      2. Pandas 提供兩種主要的資料結構,Series 與 DataFrame。Series 顧名思義就是用來處理時間序列相關的資料(如感測器資料等),主要為建立索引的一維陣列。DataFrame 則是用來處理結構化(Table like)的資料,有列索引與欄標籤的二維資料集,例如關聯式資料庫、CSV 等等。
      3. 透過載入至 Pandas 的資料結構物件後,可以透過結構化物件所提供的方法,來快速地進行資料的前處理,如資料補值,空值去除或取代等。
      4. 更多的輸入來源及輸出整合性,例如:可以從資料庫讀取資料進入 Dataframe,也可將處理完的資料存回資料庫。

8.3. Pandas 資料結構

Pandas 兩類資料結構2, 35:

  1. Series: 用來處理時間序列相關的資料(如感測器資料等),主要為建立索引的一維陣列。
  2. DataFrame: 是一個二維標籤資料結構,可以具有不同類型的行(column),類似 Excel的資料表,對於有使用過統計軟體的分析人員應該不陌生。簡單來說,Series 可以想像為一行多列(row)的資料,而 DataFrame 是多行多列的資料,藉由選擇索引(列標籤)和行(行標籤)的參數來操作資料,就像使用統計軟體透過樣本編號或變項名稱來操作資料。

8.4. Series

  1. 建立: 資料類型可為 array, dictionary
    • 由 array 建立 Series
      import pandas as pd
      
      cars = ["SAAB", "Audi", "BMW", "BENZ", "Toyota", "Nissan", "Lexus"]
      print("資料型別:", type(cars))
      carsToSeries = pd.Series(cars)
      print("資料型別:", type(carsToSeries))
      print(carsToSeries.shape)
      print(carsToSeries)
      print("carsToSeries[1]: ", carsToSeries[1])
      
      資料型別: <class 'list'>
      資料型別: <class 'pandas.core.series.Series'>
      (7,)
      0      SAAB
      1      Audi
      2       BMW
      3      BENZ
      4    Toyota
      5    Nissan
      6     Lexus
      dtype: object
      Audi
      
  2. 資料篩選
    • 由dict來建立pandas.Series()

      就是把dict的key當成index

      import pandas as pd
      
      dict = {
          "city": "Kaohsiung",
          "speedCamera1": "13213",
          "speedCamera2": "3242",
          "speedCamera3": "134343",
          "speedCamera4": "4312",
          "speedCamera5": "533"
      }
      
      dictToSeries = pd.Series(dict, index = dict.keys()) # 排序與原 dict 相同
      print(dictToSeries[0])
      print("=====")
      print(dictToSeries['speedCamera1'])
      print("=====")
      print(dictToSeries[[0, 2, 4]])
      print("=====")
      print(dictToSeries[['city', 'speedCamera1', 'speedCamera3']])
      print("=====")
      print(dictToSeries[:2])
      print("=====")
      print(dictToSeries[4:])
      # 查詢
      print("====會發現有一個NaN=====")
      search=['speedCamera1', 'speedCamera6']
      print(pd.Series(dictToSeries, index=search))
      
      Kaohsiung
      =====
      13213
      =====
      city            Kaohsiung
      speedCamera2         3242
      speedCamera4         4312
      dtype: object
      =====
      city            Kaohsiung
      speedCamera1        13213
      speedCamera3       134343
      dtype: object
      =====
      city            Kaohsiung
      speedCamera1        13213
      dtype: object
      =====
      speedCamera4    4312
      speedCamera5     533
      dtype: object
      ====會發現有一個NaN=====
      speedCamera1    13213
      speedCamera6      NaN
      dtype: object
      

8.5. DataFrame

  1. DataFrame 建立

    可利用 Dictionary 或是 Array 來建立,並使用 DataFrame 的方法來操作資料查看、資料篩選、資料切片、資料排序等運算。

  2. 由 Dictionary 建立
    import pandas as pd
    
    hobby = ["Movies", "Sports", "Coding", "Fishing", "Dancing", "cooking"]
    count = [46, 8, 12, 12, 6, 58]
    
    dict = {"hobby": hobby,
            "count": count
           }
    print(dict)
    df = pd.DataFrame(dict)
    print(df)
    
    {'hobby': ['Movies', 'Sports', 'Coding', 'Fishing', 'Dancing', 'cooking'], 'count': [46, 8, 12, 12, 6, 58]}
         hobby  count
    0   Movies     46
    1   Sports      8
    2   Coding     12
    3  Fishing     12
    4  Dancing      6
    5  cooking     58
    
  3. 由 Array 建立
    import pandas as pd
    
    ary = [["Movies", 46],["Sports", 8], ["Coding", 12], ["Fishing",12], ["Dancing",6], ["cooking",8]]
    print(ary)
    ary2df = pd.DataFrame(ary, columns = ["hobby", "count"]) # 單純的二維矩陣沒有欄位名稱,要外加指定欄標籤名稱
    print(ary2df)
    
    
    [['Movies', 46], ['Sports', 8], ['Coding', 12], ['Fishing', 12], ['Dancing', 6], ['cooking', 8]]
         hobby  count
    0   Movies     46
    1   Sports      8
    2   Coding     12
    3  Fishing     12
    4  Dancing      6
    5  cooking      8
    
  4. 讀入外部資料檔來建立 Dataframe
    • 本機CSV(由本機端的PyCharm執行,不能在Google colab上跑)
      • 可以從異質資料來源讀取檔案(如 CSV 檔)內容,並將資料放入 DataFrame 中,進行資料查看、資料篩選、資料切片等運算。
      • 如果資料檔和程式放在同一目錄(資料夾)
      • 如果資料檔放在本機在其他地方
        • 絕對路徑
        • 相對路徑
      • 下載範例檔:scores.csv
      import pandas as pd
      
      # 讀取csv
      df = pd.read_csv("/Users/student/Downloads/scores.csv") #這裡的路徑要自行更改
      print("===全部資料===")
      print(df)
      
      
      ===全部資料===
                  id  classparti  typing  homework  midexam  finalexam
      0    201910901         100      72     92.11     48.0       16.0
      1    201910902          96      56     93.42     60.0       40.0
      2    201910903          96      76    102.63     45.0       28.0
      3    201910904          93      64     86.84     44.0       25.0
      4    201910905          93      56     42.11      0.0       20.0
      ..         ...         ...     ...       ...      ...        ...
      207  201911726         106      80    102.63     96.5      103.0
      208  201911727          82      60    102.63     84.0      100.0
      209  201911728          83      48    102.63     83.0       75.0
      210  201911729          87      84    105.26    112.3      103.0
      211  201911730          96      72    102.63     91.0      100.0
      
      [212 rows x 6 columns]
      
    • 線上CSV
      import pandas as pd
      docURL = 'https://letranger.github.io/PythonCourse/scores.csv' #直接指定檔案的URL
      df = pd.read_csv(docURL)
      print('=====dataFrame的維度')
      print(df.shape)
      print('=====dataFrame的數值分佈概況')
      print(df.describe())
      print('=====dataFrame的前5筆記錄')
      print(df.head(5))
      
      =====dataFrame的維度
      (212, 6)
      =====dataFrame的數值分佈概況
                       id  classparti     typing    homework     midexam   finalexam
      count  2.120000e+02  212.000000  212.00000  212.000000  211.000000  212.000000
      mean   2.019113e+08   92.386792   82.09434   89.394764   62.863033   49.458491
      std    2.933047e+02   13.798874   23.06041   19.471763   34.104173   34.831187
      min    2.019109e+08   14.000000    0.00000   10.750000    0.000000    0.000000
      25%    2.019110e+08   87.000000   68.00000   83.880000   35.000000   20.000000
      50%    2.019111e+08   94.000000   80.00000   97.370000   67.000000   45.000000
      75%    2.019116e+08  101.000000   96.00000  102.630000   95.000000   76.000000
      max    2.019117e+08  120.000000  120.00000  107.890000  113.500000  115.000000
      =====dataFrame的前5筆記錄
                id  classparti  typing  homework  midexam  finalexam
      0  201910901         100      72     92.11     48.0       16.0
      1  201910902          96      56     93.42     60.0       40.0
      2  201910903          96      76    102.63     45.0       28.0
      3  201910904          93      64     86.84     44.0       25.0
      4  201910905          93      56     42.11      0.0       20.0
      

8.6. DataFrame 的資料瀏覽

  1. 基本函數
    • shape
    • describe()
    • head()
    • tail()
    • columns
    • index
    • info()
    import pandas as pd
    docURL = 'https://letranger.github.io/PythonCourse/scores.csv'
    df = pd.read_csv(docURL)
    
    print("====df.shape====")
    print(df.shape) # 回傳列數與欄數
    print("====df.describe()====")
    print(df.describe()) # 回傳描述性統計
    print("====df.head(3)====")
    print(df.head(3)) # 回傳前三筆觀測值
    print("====df.tail(3)====")
    print(df.tail(3)) # 回傳後三筆觀測值
    print("====df.columns====")
    print(df.columns) # 回傳欄位名稱
    print("====df.columns====")
    print(df.index) # 回傳 index
    print("====df.info====")
    print(df.info) # 回傳資料內容
    
    ====df.shape====
    (212, 6)
    ====df.describe()====
                     id  classparti     typing    homework     midexam   finalexam
    count  2.120000e+02  212.000000  212.00000  212.000000  211.000000  212.000000
    mean   2.019113e+08   92.386792   82.09434   89.394764   62.863033   49.458491
    std    2.933047e+02   13.798874   23.06041   19.471763   34.104173   34.831187
    min    2.019109e+08   14.000000    0.00000   10.750000    0.000000    0.000000
    25%    2.019110e+08   87.000000   68.00000   83.880000   35.000000   20.000000
    50%    2.019111e+08   94.000000   80.00000   97.370000   67.000000   45.000000
    75%    2.019116e+08  101.000000   96.00000  102.630000   95.000000   76.000000
    max    2.019117e+08  120.000000  120.00000  107.890000  113.500000  115.000000
    ====df.head(3)====
              id  classparti  typing  homework  midexam  finalexam
    0  201910901         100      72     92.11     48.0       16.0
    1  201910902          96      56     93.42     60.0       40.0
    2  201910903          96      76    102.63     45.0       28.0
    ====df.tail(3)====
                id  classparti  typing  homework  midexam  finalexam
    209  201911728          83      48    102.63     83.0       75.0
    210  201911729          87      84    105.26    112.3      103.0
    211  201911730          96      72    102.63     91.0      100.0
    ====df.columns====
    Index(['id', 'classparti', 'typing', 'homework', 'midexam', 'finalexam'], dtype='object')
    ====df.columns====
    RangeIndex(start=0, stop=212, step=1)
    ====df.info====
    <bound method DataFrame.info of             id  classparti  typing  homework  midexam  finalexam
    0    201910901         100      72     92.11     48.0       16.0
    1    201910902          96      56     93.42     60.0       40.0
    2    201910903          96      76    102.63     45.0       28.0
    3    201910904          93      64     86.84     44.0       25.0
    4    201910905          93      56     42.11      0.0       20.0
    ..         ...         ...     ...       ...      ...        ...
    207  201911726         106      80    102.63     96.5      103.0
    208  201911727          82      60    102.63     84.0      100.0
    209  201911728          83      48    102.63     83.0       75.0
    210  201911729          87      84    105.26    112.3      103.0
    211  201911730          96      72    102.63     91.0      100.0
    
    [212 rows x 6 columns]>
    
  2. DataFrame 資料排序
    • sort_index(): 依欄位名稱或index值來排。The axis along which to sort. The value 0 identifies the rows, and 1 identifies the columns.
    • sort_values(): 依欄位的值來排序(較常用)
    • sort 後的結果為 複本 ,不改變原本的資料
    import pandas as pd
    docURL = 'https://letranger.github.io/PythonCourse/scores.csv'
    df = pd.read_csv(docURL)
    
    print("====Original====")
    print(df.head())
    print("===.sort_index()====")
    print(df.sort_index(axis = 1, ascending = True))
    print("===.sort_index()====")
    print(df.sort_index(axis = 0, ascending = False))
    print("===.sort_values()====")
    print(df.sort_values(by = 'finalexam'))
    
    ====Original====
              id  classparti  typing  homework  midexam  finalexam
    0  201910901         100      72     92.11     48.0       16.0
    1  201910902          96      56     93.42     60.0       40.0
    2  201910903          96      76    102.63     45.0       28.0
    3  201910904          93      64     86.84     44.0       25.0
    4  201910905          93      56     42.11      0.0       20.0
    ===.sort_index()====
         classparti  finalexam  homework         id  midexam  typing
    0           100       16.0     92.11  201910901     48.0      72
    1            96       40.0     93.42  201910902     60.0      56
    2            96       28.0    102.63  201910903     45.0      76
    3            93       25.0     86.84  201910904     44.0      64
    4            93       20.0     42.11  201910905      0.0      56
    ..          ...        ...       ...        ...      ...     ...
    207         106      103.0    102.63  201911726     96.5      80
    208          82      100.0    102.63  201911727     84.0      60
    209          83       75.0    102.63  201911728     83.0      48
    210          87      103.0    105.26  201911729    112.3      84
    211          96      100.0    102.63  201911730     91.0      72
    
    [212 rows x 6 columns]
    ===.sort_index()====
                id  classparti  typing  homework  midexam  finalexam
    211  201911730          96      72    102.63     91.0      100.0
    210  201911729          87      84    105.26    112.3      103.0
    209  201911728          83      48    102.63     83.0       75.0
    208  201911727          82      60    102.63     84.0      100.0
    207  201911726         106      80    102.63     96.5      103.0
    ..         ...         ...     ...       ...      ...        ...
    4    201910905          93      56     42.11      0.0       20.0
    3    201910904          93      64     86.84     44.0       25.0
    2    201910903          96      76    102.63     45.0       28.0
    1    201910902          96      56     93.42     60.0       40.0
    0    201910901         100      72     92.11     48.0       16.0
    
    [212 rows x 6 columns]
    ===.sort_values()====
                id  classparti  typing  homework  midexam  finalexam
    81   201911108          95     100     66.89      0.0        0.0
    36   201910937          89      84     97.37      4.0        0.0
    139  201911330          95      72     51.32     30.0        0.0
    45   201911009          71      64     99.78     36.2        0.0
    49   201911013          88      76     70.61     15.0        0.0
    ..         ...         ...     ...       ...      ...        ...
    196  201911715         106     120    107.89    102.4      105.0
    195  201911714         102      44    107.89    110.0      110.0
    88   201911115         120      88    105.26    113.5      111.0
    143  201911334         100     120     81.58    104.8      113.0
    98   201911125         119      80    107.89    113.2      115.0
    
    [212 rows x 6 columns]
    
  3. DataFrame 處理遺漏值
    import pandas as pd
    docURL = 'https://letranger.github.io/PythonCourse/scores-null.csv'
    df = pd.read_csv(docURL)
    
    #df = pd.read_csv('scores-null.csv')
    print("====原始資料====")
    print(df)
    # 刪除有遺失值的記錄
    print("====刪除有遺失值的記錄====")
    dropValue = df.dropna()
    print(dropValue)
    fill0 = df.fillna(0)
    print("====遺失值填零====")
    print(fill0)
    fillv = df.fillna({"classparti":999, "typing":0, "finalExam":"NULL"})
    print("====遺失值填特定值====")
    print(fillv)
    
    ====原始資料====
              id  classparti  typing  homework  finalExam
    0  201910901       100.0    72.0     92.11       48.0
    1  201910902        96.0    56.0     93.42       60.0
    2  201910903        96.0    76.0    102.63        NaN
    3  201910904         NaN    64.0     86.84       44.0
    4  201910905        93.0    56.0     42.11        0.0
    5  201910906       101.0   108.0    100.00        NaN
    6  201910907       101.0     NaN     92.11       55.0
    7  201910908        94.0    68.0    105.26       61.0
    8  201910909         NaN    64.0     44.74       20.0
    9  201910910        93.0   120.0     97.37       16.0
    ====刪除有遺失值的記錄====
              id  classparti  typing  homework  finalExam
    0  201910901       100.0    72.0     92.11       48.0
    1  201910902        96.0    56.0     93.42       60.0
    4  201910905        93.0    56.0     42.11        0.0
    7  201910908        94.0    68.0    105.26       61.0
    9  201910910        93.0   120.0     97.37       16.0
    ====遺失值填零====
              id  classparti  typing  homework  finalExam
    0  201910901       100.0    72.0     92.11       48.0
    1  201910902        96.0    56.0     93.42       60.0
    2  201910903        96.0    76.0    102.63        0.0
    3  201910904         0.0    64.0     86.84       44.0
    4  201910905        93.0    56.0     42.11        0.0
    5  201910906       101.0   108.0    100.00        0.0
    6  201910907       101.0     0.0     92.11       55.0
    7  201910908        94.0    68.0    105.26       61.0
    8  201910909         0.0    64.0     44.74       20.0
    9  201910910        93.0   120.0     97.37       16.0
    ====遺失值填特定值====
              id  classparti  typing  homework finalExam
    0  201910901       100.0    72.0     92.11      48.0
    1  201910902        96.0    56.0     93.42      60.0
    2  201910903        96.0    76.0    102.63      NULL
    3  201910904       999.0    64.0     86.84      44.0
    4  201910905        93.0    56.0     42.11       0.0
    5  201910906       101.0   108.0    100.00      NULL
    6  201910907       101.0     0.0     92.11      55.0
    7  201910908        94.0    68.0    105.26      61.0
    8  201910909       999.0    64.0     44.74      20.0
    9  201910910        93.0   120.0     97.37      16.0
    

8.7. Dataframe 的選取與過濾

  1. 資料選取: Select column(s) 36
    • df[['col1', 'col2']]
    • df.col1
    import pandas as pd
    docURL = 'https://letranger.github.io/PythonCourse/scores.csv'
    df = pd.read_csv(docURL)
    
    print(df.head(3))
    print(df[['id', 'typing']].head(3))
    print(df.id, df.homework)
    
              id  classparti  typing  homework  midexam  finalexam
    0  201910901         100      72     92.11     48.0       16.0
    1  201910902          96      56     93.42     60.0       40.0
    2  201910903          96      76    102.63     45.0       28.0
              id  typing
    0  201910901      72
    1  201910902      56
    2  201910903      76
    0      201910901
    1      201910902
    2      201910903
    3      201910904
    4      201910905
             ...
    207    201911726
    208    201911727
    209    201911728
    210    201911729
    211    201911730
    Name: id, Length: 212, dtype: int64 0       92.11
    1       93.42
    2      102.63
    3       86.84
    4       42.11
            ...
    207    102.63
    208    102.63
    209    102.63
    210    105.26
    211    102.63
    Name: homework, Length: 212, dtype: float64
    
  2. 資料選取: Select using index (row)
    • df[1:20]
    import pandas as pd
    docURL = 'https://letranger.github.io/PythonCourse/scores.csv'
    df = pd.read_csv(docURL)
    
    print(df[1:3])
    print(df[df.homework<30])
    # 合併row and column
    print(df.id[df.homework<30])
    print(df[['id', 'homework']][1:3])
    
              id  classparti  typing  homework  midexam  finalexam
    1  201910902          96      56     93.42     60.0       40.0
    2  201910903          96      76    102.63     45.0       28.0
                id  classparti  typing  homework  midexam  finalexam
    10   201910911          62      44     20.39      4.0        0.0
    18   201910919          14      56     25.92      0.0        0.0
    23   201910924          75      48     10.75      0.0        0.0
    124  201911315          71     120     15.35      0.0        0.0
    140  201911331          93     120     25.88      0.0       16.0
    10     201910911
    18     201910919
    23     201910924
    124    201911315
    140    201911331
    Name: id, dtype: int64
              id  homework
    1  201910902     93.42
    2  201910903    102.63
    
  3. 條件式選取資料
    • 語法
      • df[(condition)]
      • df[(condition 1) & (condition 2) ]
    • 範例
      import pandas as pd
      
      docURL = 'https://letranger.github.io/PythonCourse/scores.csv'
      df = pd.read_csv(docURL)
      
      print(df[(df.midexam >= 100) & (df.finalexam >= 100)])
      
                  id  classparti  typing  homework  midexam  finalexam
      11   201910912          87      92     92.11    110.8     103.75
      70   201911034         104      92    102.63    100.8     101.00
      88   201911115         120      88    105.26    113.5     111.00
      98   201911125         119      80    107.89    113.2     115.00
      102  201911129         106      52    102.63    103.6     100.00
      127  201911318         108      80    102.63    104.8     100.00
      138  201911329         105      48    107.89    100.0     100.00
      143  201911334         100     120     81.58    104.8     113.00
      153  201911608          88       0    102.63    100.0     100.00
      169  201911624          82     100    102.63    101.5     105.00
      182  201911701         103     108     92.63    100.0     100.00
      189  201911708         106     108    102.63    107.2     101.00
      190  201911709         108      88    107.89    104.8     105.00
      191  201911710         102     100    102.63    100.0     105.00
      193  201911712         105      96    101.58    107.5     100.00
      195  201911714         102      44    107.89    110.0     110.00
      196  201911715         106     120    107.89    102.4     105.00
      198  201911717          80     120    107.89    106.0     100.00
      201  201911721         109      76    100.00    100.0     100.00
      203  201911722         105     120    102.63    101.5     105.00
      210  201911729          87      84    105.26    112.3     103.00
      

8.8. Dataframe 進階條件過濾

  1. loc, iloc, between 37
    • loc: 基於行標籤和列標籤(x_label、y_label)進行索引,以 column 名做為 index
    • iloc: 基於行索引和列索引(index,columns) 都是從 0 開始,以數字做為 index
    • between: 檢查區間值, Series.between(self, left, right, inclusive=True)
    • 範例 1
      import pandas as pd
      
      docURL = 'https://letranger.github.io/PythonCourse/scores.csv'
      df = pd.read_csv(docURL)
      
      print(df.head(3))
      print("========")
      print(df.loc[df.homework<25, ['id', 'homework']])
      print("========")
      print(df.loc[2,'homework'])
      
                id  classparti  typing  homework  midexam  finalexam
      0  201910901         100      72     92.11     48.0       16.0
      1  201910902          96      56     93.42     60.0       40.0
      2  201910903          96      76    102.63     45.0       28.0
      ========
                  id  homework
      10   201910911     20.39
      23   201910924     10.75
      124  201911315     15.35
      ========
      102.63
      
    • 範例 2
      # 載入函式庫
      import pandas as pd
      
      # 讀取csv
      docURL = 'https://letranger.github.io/PythonCourse/scores.csv'
      df = pd.read_csv(docURL)
      
      # 輸出前3筆
      print(df.iloc[:3])
      # 輸出前3筆的第2,3欄
      print(df.iloc[:2, 1:3])
      
      print(df['midexam'].between(50, 60))
      midFilter = df['midexam'].between(50, 55)
      print(df[midFilter]) #也可以直接寫成 df[ df['midexam'].between(50, 55)], 和NumPy的語法類似
      
      Pandas version: 0.25.1
                id  classparti  typing  homework  midexam  finalexam
      0  201910901         100      72     92.11     48.0       16.0
      1  201910902          96      56     93.42     60.0       40.0
      2  201910903          96      76    102.63     45.0       28.0
         classparti  typing
      0         100      72
      1          96      56
      102.63
      0      False
      1       True
      2      False
      3      False
      4      False
             ...
      207    False
      208    False
      209    False
      210    False
      211    False
      Name: midexam, Length: 212, dtype: bool
                  id  classparti  typing  homework  midexam  finalexam
      6    201910907         101     120     92.11     55.0       20.0
      65   201911029          93      60     82.00     50.0       16.0
      72   201911036          88      80    102.63     53.0       76.0
      92   201911119          82     104     89.47     50.0       20.0
      94   201911121          95     100     88.51     52.0       72.0
      109  201911136          91     100     81.58     51.0        4.0
      171  201911626          98      72     97.37     50.0       75.0
      

8.9. 進階分析

  1. 由其他 column 產生新的 column(重要、實用)
    • 全部指定
    • 條件指定
  2. 資料分組: groupby
  3. 範例
    # 載入函式庫
    import pandas as pd
    
    # 讀取csv
    docURL = 'https://letranger.github.io/PythonCourse/scores.csv'
    df = pd.read_csv(docURL)
    
    # 計算總分'
    df['Final'] = df.classparti*.1 + df.typing*.1 + df.homework*.2 + df.midexam*.3 + df.finalexam*.3
    
    # PASS/FAIL
    df.loc[df.Final < 60, 'PASS'] = False
    df.loc[df.Final >= 60, 'PASS'] = True
    
    # 新增一個打字速度的欄位
    df.loc[df.typing < 30, 'TypingSpeed' ] = 'LOW'
    df.loc[df.typing.between(30, 60), 'TypingSpeed' ] = 'MID'
    df.loc[df.typing > 60, 'TypingSpeed' ] = 'HIGH'
    
    print(df.head(3))
    
    # 依打字速度分組,看不同組別的學期總成績分佈
    print('---依打字速度分組---')
    print(df.groupby('TypingSpeed')['Final'].mean())
    print(df.groupby('PASS')['typing' ,'homework'].mean())
    
    # 罝換row/column
    print('---置換row/column(轉90度)---')
    print(df.groupby('PASS')['typing' ,'homework'].mean().T)
    
    
              id  classparti  typing  homework  midexam  finalexam
    0  201910901         100      72     92.11     48.0       16.0
    1  201910902          96      56     93.42     60.0       40.0
    2  201910903          96      76    102.63     45.0       28.0
              id  classparti  typing  ...   Final   PASS  TypingSpeed
    0  201910901         100      72  ...  54.822  False         HIGH
    1  201910902          96      56  ...  63.884   True          MID
    2  201910903          96      76  ...  59.626  False         HIGH
    
    [3 rows x 9 columns]
    ---依打字速度分組---
    TypingSpeed
    HIGH    69.977451
    LOW     68.517333
    MID     64.086303
    Name: Final, dtype: float64
              typing   homework
    PASS
    False  77.567568  76.409054
    True   84.525547  96.350730
    ---置換row/column(轉90度)---
    PASS          False       True
    typing    77.567568  84.525547
    homework  76.409054  96.350730
    
  4. 資料檔下載: scores.csv

8.10. 資料視覺化

  1. Bar chart
    # 載入函式庫
    import pandas as pd
    
    # 讀取csv
    docURL = 'https://letranger.github.io/PythonCourse/scores.csv'
    df = pd.read_csv(docURL)
    
    # 計算總分'
    df['Final'] = df.classparti*.1 + df.typing*.1 + df.homework*.2 + df.midexam*.3 + df.finalexam*.3
    
    # create new column according to typing speed
    df.loc[df.typing < 30, 'TypingSpeed' ] = 'Level-1'
    df.loc[df.typing.between(30, 60), 'TypingSpeed' ] = 'Level-2'
    df.loc[df.typing > 60, 'TypingSpeed' ] = 'Level-3'
    
    x = df.groupby('TypingSpeed')['homework','classparti'].mean()
    print(x)
    # 關於圖表的屬性設定方式
    fig = df.groupby('TypingSpeed')['homework','classparti'].mean().plot(kind='bar', title="XXX", rot=0, legend=True)
    fig.set_xlabel("Typing Speed")
    fig.set_ylabel("score")
    # savefig = fig.get_figure() # 如果不存圖檔就不用設定這行
    # 關於圖表的屬性設定方式
    fig1 = df.groupby('TypingSpeed')['homework','classparti'].mean().T.plot(kind='bar', title="XXX", rot=0, legend=True)
    fig1.set_xlabel("Subject")
    fig1.set_ylabel("score")
    
     # 如果不存圖檔就不用執行以下程式
    #savefig1 = fig1.get_figure()
    #savefig.savefig('images/pandasPlot1.png', bbox_inches='tight')
    #savefig1.savefig('images/pandasPlot10.png', bbox_inches='tight')
    
                  homework  classparti
    TypingSpeed
    Level-1      96.053333   87.666667
    Level-2      85.273939   88.696970
    Level-3      90.053920   93.159091
    

    pandasPlot1.png

    Figure 27: Pandas plot bar chart

    pandasPlot10.png

    Figure 28: Pandas plot bar chart

  2. Pie chart
    • DEMO
      # 載入函式庫
      import pandas as pd
      
      # 讀取csv
      docURL = 'https://letranger.github.io/PythonCourse/scores.csv'
      df = pd.read_csv(docURL)
      
      # 計算總分'
      df['Final'] = df.classparti*.1 + df.typing*.1 + df.homework*.2 + df.midexam*.3 + df.finalexam*.3
      
      # create new column according to typing speed
      df.loc[df.typing < 60, 'TypingSpeed' ] = 'Level-1'
      df.loc[df.typing.between(60, 80), 'TypingSpeed' ] = 'Level-2'
      df.loc[df.typing > 80, 'TypingSpeed' ] = 'Level-3'
      print(df.groupby('TypingSpeed').count())
      df.groupby('TypingSpeed')['TypingSpeed'].count().plot(kind='pie', title="XXX", rot=0, legend=True)
      # 如果不存圖檔就不用執行以下程式
      # fig = df.groupby('TypingSpeed')['TypingSpeed'].count().plot(kind='pie', title="XXX", rot=0, legend=True)
      # savefig = fig.get_figure()
      # savefig.savefig('images/pandasPlot2.png', bbox_inches='tight')
      

      id classparti typing homework midexam finalexam Final
      TypingSpeed
      Level-1 25 25 25 25 25 25 25
      Level-2 86 86 86 86 86 86 86
      Level-3 101 101 101 101 100 101 100

      pandasPlot2.png

      Figure 29: Pandas plot pie chart

    • 如何避免圖例擋住圖表
  3. Scatter chart
    # 載入函式庫
    import pandas as pd
    
    # 讀取csv
    docURL = 'https://letranger.github.io/PythonCourse/scores.csv'
    df = pd.read_csv(docURL)
    
    df[['midexam','finalexam']].plot(kind='scatter', x=1, y=0)
    # 如果不存圖檔就不用執行以下程式
    #fig = df[['midexam','finalexam']].plot(kind='scatter', x=1, y=0)
    #savefig = fig.get_figure()
    #savefig.savefig('images/PandasPlot3.png')
    

    PandasPlot3.png

    Figure 30: Pandas plot scatter chart

  4. Haxbin chart
    # 載入函式庫
    import pandas as pd
    
    # 讀取csv
    docURL = 'https://letranger.github.io/PythonCourse/scores.csv'
    df = pd.read_csv(docURL)
    
    df[['midexam','finalexam']].plot(kind='hexbin', x=1, y=0, gridsize=25)
    
    #fig = df[['midexam','finalexam']].plot(kind='hexbin', x=1, y=0, gridsize=25)
    #savefig = fig.get_figure()
    #savefig.savefig('images/PandasPlot6.png')
    

    PandasPlot6.png

    Figure 31: Pandas plot hexbin chart

  5. Regression line chart
    # 載入函式庫
    import pandas as pd
    import seaborn as sns
    
    # 讀取csv
    docURL = 'https://letranger.github.io/PythonCourse/scores.csv'
    df = pd.read_csv(docURL)
    
    sns.regplot(x="midexam", y='finalexam', data=df);
    
    #fig = sns.regplot(x="midexam", y='finalexam', data=df);
    #savefig = fig.get_figure()
    #savefig.savefig('images/PandasPlot4.png')
    
    

    PandasPlot4.png

    Figure 32: Pandas plot scatter chart

8.11. 實作練習

  1. 403 期中考得分統計
    • 原始數據

    csv.jpg

    Figure 33: CSV 內容

    • cs109score.csv為 T 市某校的資訊科線上期中考系統所匯出的成績檔,該次考試共計 10 題,
      其中 A~F 為基本題,B1~B4 為加分題,前 5 題(A,B,C,D,E)為基本題,每題 100 分,後 4 題
      為加分題(Bonus1~Bonus4),每題 25 分,本份考卷總分:600;總得分除以 5 即為期中考得分,此次期中考總分 120 分。
    • 任務 1: [直接輸出結果]將有缺失值的部份以 0 分取代。
    • 任務 2: [直接輸出結果]求出每個人的原始總分(即所有分數),新增的欄位名稱為TOTAL。
    • 任務 3: [直接輸出結果]求出每個人的實際得分(原始總分TOTAL除以 5)。
    • 任務 4: [直接輸出結果]由學號欄中提出班級資料, 新增一欄班級欄(欄位名稱CLAS),班級為學號的第 5~7 碼,如 201910107,
      表示該生為 101 班。
    • 任務 5: 求出各題的平均得分,以直條長條圖顯示。如下圖:

      task5.png

      Figure 34: task5

    • 任務 6: 以班級為分組依據,畫出各班平均得分,以橫向長條圖顯示。如下圖:

      task6.png

      Figure 35: task6

    • 任務 7: 求 119 班各題平均得分,以 pie chart 顯示。如下圖:

      task7.png

      Figure 36: task7

    • 任務 8: 求所有受試者第 B 及 B1 兩題的 scatter plot 以及 regression line。如下圖:

      task8.png

      Figure 37: task8

  2. 進階練習

    一dataframe如下

    import numpy as np
    import pandas as pd
    scores = {'Math': [900, 50, 70, 80],
              'English': [60, 30, 90, 50],
              'History': [33, 75, np.NaN, np.NaN]}
    df = pd.DataFrame(scores, index=['Simon', 'Allen', 'Jimmy', 'Vanessa'])
    

    試解決以下問題:

    1. 列出Math分數>100資料
    2. 將Siman之Math改為90分
    3. 計算History遺漏值筆數
    4. 以History平均值取代History中遺漏值
    5. 新增“平均”欄位,計算四人各科平均
    6. 新增“名次”欄位,以四人平均分數為排序依據
    7. 列出平均不及格者
    8. 列出數學最高分者姓名

9. 網路資料解析與爬蟲

9.1. JSON

  1. 網路資料分析的兩種類型
    • 可直接下載的靜態結構化資料,如 CSV、JSON、XML
    • 直接分析網站的線上內容(HTML): 爬蟲(Web Crawler)
  2. JSON
    • What is JSON?
      • JSON(JavaScript Object Notation,JavaScript 物件表示法)是個以純文字來描述資料的簡單結構,在 JSON 中可以透過特定的格式去儲存任何資料(字串,數字,陣列,物件),也可以透過物件或陣列來傳送較複雜的資料。38
      • JSON 常用於網站上的資料呈現、傳輸 (例如將資料從伺服器送至用戶端,以利顯示網頁)。
      • 一旦建立了 JSON 資料,就可以非常簡單的跟其他程式溝通或交換資料,因為 JSON 就只是純文字格式。
    • JSON 的優點
      • 相容性高
      • 格式容易瞭解,閱讀及修改方便
      • 支援許多資料格式 (number,string,booleans,nulls,array,associative array)
      • 許多程式都支援函式庫讀取或修改 JSON 資料
      • NoSQL Database
    • JSON 結構
      • 物件: {}
      • 陣列: []
    • JSON 支援的資料格式
      • 字串 (string),要以雙引號括起來:
        • {“name”: “Mike”}
      • 數值 (number)
        • {“age”: 25}
      • 布林值 (boolean)
        • {“pass”: True}
      • 空值 (null)
        • {“middlename”: null}
      • 物件 (object)
      • 陣列 (array)
    • JSON 物件
      • 格式
      • key 只能是字串,也一定要加上雙引號
      {
          "key1": value1,
          "key2": value2,
          ......
          "keyN": valueN
      }
      
      • 範例 1
      {
          "id": 1,
          "name": "Jamees, Yen",
          "age": 19,
          "gender": "M",
          "hobby": ["music", "programming"]
      }
      
      • 範例 2
      {
          "id": 382192
          "name": "Jamees, Yen",
          "age": 19,
          "gender": "M",
          "exams": [
              { "title": "期中考",
                "chinese": 85,
                "math": 98,
                "englihs": 92
              },
              { "title": "期末考",
                "chinese": 81,
                "math": 92,
                "englihs": 97
              }
          ],
          "hobby": ["music", "programming"]
      }
      
    • JSON轉PANDAS dataFrame
      from pandas.io.json import json_normalize
      
      df = json_normalize( list of dict )
      print(df.head(3))
      
  3. JSON實作範例
    • 實作 1: 國際主要國家貨幣每月匯率概況
      • 下載 JSON
        # -*- coding: utf-8 -*-
        import requests
        
        json_url = 'https://quality.data.gov.tw/dq_download_json.php?nid=11339&md5_url=f2fdbc21603c55b11aead08c84184b8f'
        response = requests.get(json_url)
        print(response)
        jsonRes = response.json()
        print(type(jsonRes))
        
        import json
        print(jsonRes[:1])
        print(json.dumps(jsonRes[:2], indent = 4, ensure_ascii=False))
        
        print('日期', ":", '美元/新台幣')
        for item in jsonRes:
            print(item['日期'], ":", item['美元/新台幣'])
        
        <Response [200]>
        <class 'list'>
        [{'日期': '20201005', '美元/新台幣': '29.02', '人民幣/新台幣': '4.29882', '歐元/美元': '1.17405', '美元/日幣': '105.645', '英鎊/美元': '1.2947', '澳幣/美元': '0.71775', '美元/港幣': '7.75005', '美元/人民幣': '6.7507', '美元/南非幣': '16.4019', '紐幣/美元': '0.66435'}]
        [
            {
                "日期": "20201005",
                "美元/新台幣": "29.02",
                "人民幣/新台幣": "4.29882",
                "歐元/美元": "1.17405",
                "美元/日幣": "105.645",
                "英鎊/美元": "1.2947",
                "澳幣/美元": "0.71775",
                "美元/港幣": "7.75005",
                "美元/人民幣": "6.7507",
                "美元/南非幣": "16.4019",
                "紐幣/美元": "0.66435"
            },
            {
                "日期": "20201006",
                "美元/新台幣": "28.96",
                "人民幣/新台幣": "4.300705",
                "歐元/美元": "1.17735",
                "美元/日幣": "105.555",
                "英鎊/美元": "1.297",
                "澳幣/美元": "0.7156",
                "美元/港幣": "7.75005",
                "美元/人民幣": "6.7338",
                "美元/南非幣": "16.63735",
                "紐幣/美元": "0.66365"
            }
        ]
        日期 : 美元/新台幣
        20201005 : 29.02
        20201006 : 28.96
        20201007 : 28.965
        20201008 : 28.966
        20201012 : 28.909
        20201013 : 28.92
        20201014 : 28.952
        20201015 : 28.96
        20201016 : 28.979
        20201019 : 28.95
        20201020 : 28.932
        20201021 : 28.892
        20201022 : 28.903
        20201023 : 28.917
        20201026 : 28.902
        20201027 : 28.872
        20201028 : 28.906
        20201029 : 28.914
        20201030 : 28.925
        20201102 : 28.909
        20201103 : 28.92
        20201104 : 29.006
        20201105 : 28.874
        20201106 : 28.876
        20201109 : 28.825
        20201110 : 28.856
        20201111 : 28.83
        20201112 : 28.86
        20201113 : 28.847
        20201116 : 28.81
        20201117 : 28.815
        20201118 : 28.758
        20201119 : 28.818
        20201120 : 28.82
        20201123 : 28.803
        20201124 : 28.831
        20201125 : 28.816
        20201126 : 28.811
        
    • 實作 2: 澎湖生活博物館每月參觀人次統計資料
      #coding:utf-8
      import requests
      import pandas as pd
      from datetime import datetime
      
      json_url = 'http://opendataap2.penghu.gov.tw/resource/files/2020-01-12/eaa641fc3af66277e60b13201ca11232.json'
      
      response = requests.get(json_url)
      jsonRes = response.json()
      
      year = [str(int(i['年度'])+1911) for i in jsonRes]
      month = [i['月份'] for i in jsonRes]
      visitor = [int(i['人數']) for i in jsonRes]
      
      dates = [x+'/'+y for x, y in zip(year, month)]
      
      from datetime import datetime
      dates = [datetime.strptime(x, '%Y/%m').date() for x in dates]
      
      import matplotlib.pyplot as plt
      
      plt.rcParams['font.family'] = 'cwTeXFangSong' #這裡要改成colab的中文化設定
      plt.clf()
      plt.barh(dates, visitor)
      plt.xticks(rotation=0, fontsize=10)
      plt.yticks(rotation=0, fontsize=6)
      plt.xlabel('人數')
      plt.ylabel('日期')
      plt.title('澎湖生活博物館每月參觀人次')
      plt.savefig('images/jsonBar.png', dpi=300, bbox_inches='tight')
      

      jsonBar.png

      Figure 38: 參觀人數

    • 實作 3: 學校甄選公告#1
      import requests
      import json
      json_url = 'http://www.kh.edu.tw/json/bulletin/employ/datagrid?page=1&rows=20'
      response = requests.get(json_url)
      
      print(response)
      # 方法1
      jsonRes1 = response.json()
      print("========\n", type(jsonRes1))
      #
      jsonRes2 = json.loads(response.text)
      print("========\n", type(jsonRes2))
      
      print("========\n", jsonRes2['rows'][:2])
      # 先將JSON的資料轉為PANDAS
      for item in jsonRes2['rows']:
          #將attributes裡的k,v移出來
          item['subject'] = item['attributes']['subjects']
          item['url'] = item['attributes']['url']
          #將attribute刪掉
          del item['attributes']
      from pandas.io.json import json_normalize
      import matplotlib.pyplot as plt
      df = json_normalize(jsonRes2['rows'][:5])
      print(df.head(3))
      
      <Response [200]>
      ========
       <class 'dict'>
      ========
       <class 'dict'>
      ========
       [{'author': '正興國中', 'attributes': {'subjects': '國文', 'url': 'https://employ.kh.edu.tw/Html/2023/3/三民區111學年度正興國中第6號第1次公告簡章.html', 'target': '_blank'}, 'title': '111學年度正興國中第6號第1次公告', 'pubDate': '112-03-09'}, {'author': '文山國小', 'attributes': {'subjects': '專任輔導代理育嬰', 'url': 'https://employ.kh.edu.tw/Html/2023/3/鳳山區111學年度文山國小第10號第2次公告簡章.html', 'target': '_blank'}, 'title': '111學年度文山國小第10號第2次公告', 'pubDate': '112-03-09'}]
        author  ...                                                url
      0   正興國中  ...  https://employ.kh.edu.tw/Html/2023/3/三民區111學年度...
      1   文山國小  ...  https://employ.kh.edu.tw/Html/2023/3/鳳山區111學年度...
      2   竹圍國小  ...  https://employ.kh.edu.tw/Html/2023/3/岡山區111學年度...
      
      [3 rows x 5 columns]
      
    • 實作 4: 學校甄選公告
      import requests
      import json
      
      response = requests.get('http://www.kh.edu.tw/json/bulletin/employ/datagrid?page=1&rows=20')
      jsonRes = response.json()
      #print(jsonRes['rows'])
      for item in jsonRes['rows']:
          print(item['author'], ":", item['title'],"/", item['pubDate'])
      #    sortJR = jsonRes['rows']
      #    print(type(sortJR))
      #
      #sortJR.sort(key=lambda x: x['author'], reverse=False)
      #for item in sortJR:
      #    print(item['author'], ":", item['title'],"/", item['pubDate'])
      
      明誠高中 : 109學年度明誠高中第1號第1次公告 / 109-06-01
      八卦國小 : 109學年度八卦國小第1號第1次公告 / 109-06-01
      新莊國小 : 109學年度新莊國小第1號第1次公告 / 109-06-01
      楠梓國中 : 108學年度楠梓國中第9號第3次公告 / 109-06-02
      燕巢國中 : 108學年度燕巢國中第14號第11次公告 / 109-05-29
      前鎮國中 : 108學年度前鎮國中第10號第1次公告 / 109-05-25
      仁武特殊教育學校 : 108學年度仁武特殊教育學校第7號第5次公告 / 109-05-12
      大社國小 : 108學年度大社國小第7號第3次公告 / 109-05-11
      明誠高中 : 108學年度明誠高中第3號第3次公告第1次修正 / 109-05-04
      鼓山高中 : 108學年度鼓山高中第11號第3次公告 / 109-05-04
      南成國小 : 108學年度南成國小第3號第3次公告 / 109-05-01
      青山國小 : 108學年度青山國小第8號第1次公告 / 109-04-30
      明宗國小 : 108學年度明宗國小第5號第1次公告 / 109-04-30
      大社國中 : 108學年度大社國中第5號第3次公告 / 109-04-29
      小港國小 : 108學年度小港國小第15號第3次公告 / 109-04-21
      小港國小 : 108學年度小港國小第13號第2次公告 / 109-04-18
      鼓山國小 : 108學年度鼓山國小第6號第3次公告 / 109-04-16
      新民國小 : 108學年度新民國小第9號第3次公告 / 109-04-14
      林園高中 : 108學年度林園高中第18號第3次公告 / 109-04-14
      大社國中 : 108學年度大社國中第4號第3次公告 / 109-04-13
      

9.2. JSON課堂練習

  • [注意]如果JSON的資料下載發生錯誤,可以試著將URL由’https://’改為’http://’
  • 高雄市政府資料開放平台找到[高雄市各級學校甄選公告],線上讀取其線上 JSON 資料,輸出校名及出缺科別。
  • 政府資料開放平台找到高雄市電動機車充電站名稱及充電站地址,線上讀取 JSON 檔,列出所有高雄市機車充電站之[計費方式]以及[充電站所在位址]。
  • 請上網查詢台南市各區WIFI熱點數量,依行政區統計,繪製類似以下圖形,儘可能加上統計圖表所需元素並加以美化

    wifis.png

    Figure 39: Caption

9.4. HTML

  1. What is HTML
    • 政府資料開放平台
    • 超文本標記語言(HyperText Markup Language)是一種用於建立網頁的標準標記語言。HTML 是一種基礎技術,常與 CSS、JavaScript 一起被眾多網站用於設計網頁、網頁應用程式以及行動應用程式的使用者介面。39
    • HTML 元素是構建網站的基石。HTML 允許嵌入圖像與物件,並且可以用於建立互動式表單,它被用來結構化資訊——例如標題、段落和列表等等,也可用來在一定程度上描述文件的外觀和語意。39
    • HTML 的語言形式為<>包圍的 HTML 元素(如<html>),瀏覽器使用 HTML 標籤和指
      令碼來詮釋網頁內容,但不會將它們顯示在頁面上。 39
    • 網頁就是由各式標籤 (tag) 所組成的階層式文件。
  2. HTML 範例
    • HTML code
    <html>
      <head>
        <title>我是網頁標題</title>
        <style>
        .large {
          color:blue;
          text-align: center;
        }
        </style>
      </head>
      <body>
        <h1 class="large">我是變色且置中的抬頭</h1>
        <p id="p1">我是段落一</p>
        <p id="p2" style="">我是段落二</p>
        <div><a href='http://blog.castman.net' style="font-size:200%;">我是放大的超連結</a></div>
      </body>
    </html>
    
    • RESULTS
    我是網頁標題

    我是變色且置中的抬頭

    我是段落一

    我是段落二

    • HTML tree

    html-tree.png

    Figure 40: HTML tree

    • HTML 文件內不同的標籤 (例如 <title>, <h1>, <p>, <a>, <div>)
    • 不同的標籤有著不同的語義,表示建構網頁用的不同元件,且標籤可以有各種屬性 (例如 id, class, style 等通用屬性, 或 href 等專屬屬性),
    • <div>是division這個單字取前面三個字母來表示,division是區分的意思,div標籤主要的功能就是在形成一個個的區塊,方便網頁排版美化40。例如:
    <div style="background-color:grey;">
        <p>今天是第7天的介紹,div範例程式</p>
        <p>今天是第7天的介紹,div範例程式</p>
    </div>
    

    其結果為:

    今天是第7天的介紹,div範例程式

    今天是第7天的介紹,div範例程式

    • 我們可以用標籤 + 屬性去定位資料所在的區塊並取得資料。41

9.5. 網路爬蟲

  1. 爬蟲精神:將程式模仿成一般 user
    • 原則
      1. 要抓網路資料,就要先仔細觀察網頁內容
      2. 要儘量欺騙網站自己是 browser
    • 實作範例: 取得 HTML 資料
      • 第一版: 以urllib模組來抓取HTML資料

        urllib.request 是一個用來從 URLs (Uniform Resource Locators)取得資料的 Python 模組。它提供一個了非常簡單的介面能接受多種不同的協議(protocol, 如 http, ftp), urlopen 函數。

        # 抓取PTT電影版header
        import urllib.request as req
        url = "https://www.ptt.cc/bbs/Movie/index.html"
        
        with req.urlopen(url) as response:
            data = response.read().decode("utf-8")
        print(data)
        

        結果得到如下錯誤訊息:

        urllib.error.HTTPError: HTTP Error 403: Forbidden
        

        被拒絕原因:這隻程式的行為不像一般使用者,被網站伺服器拒絕。403 Forbidden 是 HTTP 協議中的一個 HTTP 狀態碼(Status Code)。可以簡單的理解為沒有權限訪問此站,服務器收到請求但拒絕提供服務42

      • 第二版:加入 headers
        import urllib.request as req
        url = "https://www.ptt.cc/bbs/Movie/index.html"
        # 幫request加上一個header
        request = req.Request(url, headers = {
            "User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:72.0) Gecko/20100101 Firefox/72.0"
        })
        
        with req.urlopen(request) as response:
            data = response.read().decode("utf-8")
        print('所取得的資料型態:',type(data))
        print('===得到的部份HTML內容===\n', data[2651:3500])
        
        所取得的資料型態: <class 'str'>
        ===得到的部份HTML內容===
         <div class="r-ent">
        			<div class="nrec"></div>
        			<div class="title">
        
        				<a href="/bbs/movie/M.1678501889.A.BD7.html">[新聞] 迪士尼、網飛都搶合作 台灣女力吳采頤</a>
        
        			</div>
        			<div class="meta">
        				<div class="author">filmwalker</div>
        				<div class="article-menu">
        
        					<div class="trigger">&#x22ef;</div>
        					<div class="dropdown">
        						<div class="item"><a href="/bbs/movie/search?q=thread%3A%5B%E6%96%B0%E8%81%9E%5D&#43;%E8%BF%AA%E5%A3%AB%E5%B0%BC%E3%80%81%E7%B6%B2%E9%A3%9B%E9%83%BD%E6%90%B6%E5%90%88%E4%BD%9C%E3%80%80%E5%8F%B0%E7%81%A3%E5%A5%B3%E5%8A%9B%E5%90%B3%E9%87%87%E9%A0%A4">搜尋同標題文章</a></div>
        
        						<div class="item"><a href="/bbs/movie/search?q=author%3Afilmwalker">搜尋看板內 filmwalker 的文章</a></div>
        
        					</div>
        
        				</div>
        				<div class="date"> 3/11</div>
        				<div class="mark"></div>
        			</div>
        		</div>
        

        可以發現抓取下來的結果都是字串型態,不太容易進一步擷取所需資訊。例如,當我們想抓取所有看板中的標題,只使用字串所提供的一些函式就很難完成工作。

  2. BeautifulSoup
    • BeautifulSoup4 簡介
      • BeautifulSoup4 和 lxml 一樣,Beautiful Soup 也是一個 HTML/XML 的解析器,
      • 主要的功能是解析和提取 HTML/XML 資料。
    • 實作:解析 HTML 資料內容
      • 安裝解析 HTML 所需套件(安裝套件名稱後面有 4)
        pip install beautifulsoup4
        
      • 第三版: 解析 HTML
        import urllib.request as req
        url = "https://www.ptt.cc/bbs/Movie/index.html"
        # 幫request加上一個header
        request = req.Request(url, headers = {
            "User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:72.0) Gecko/20100101 Firefox/72.0"
        })
        
        with req.urlopen(request) as response:
            data = response.read().decode("utf-8")
        
        import bs4
        root = bs4.BeautifulSoup(data,"html.parser")
        
        # 依序執行丁列程式
        print(type(root)) #取得的資料型態變成是bs4的物件,而非字串
        print(type(root.prettify())) #可以先美化輸出,方便查看HTML結構
        print(root.prettify()[:100])
        
        <class 'bs4.BeautifulSoup'>
        <class 'str'>
        <!DOCTYPE html>
        <html>
         <head>
          <meta charset="utf-8"/>
          <meta content="width=device-width, initia
        
        <class 'bs4.BeautifulSoup'>
        <class 'str'>
        
    • Beautifulsoup 解析器

      現在我們可以進一步來研究bs4的玩法

      • 在上面的語句中,我們使用了一個 html.parser。 這是一個解析器,在構造BeautifulSoup 物件的時候,需要用到解析器。BeautifulSoup 支援 python 內建的解析器和少數第三方解析器。43:

      parsers.jpg

      Figure 41: Parsers 比較

      • 一般來說,對於速度或效能要求不太高的話,也可以使用 html5lib 來進行解析;如果較在乎效能則建議使用 lxml 來進行解析。

      如果要換用html5lib,則要先安裝:

      1: pip install html5lib
      

      接下來更換parser:

      import urllib.request as req
      url = "https://www.ptt.cc/bbs/Movie/index.html"
      # 幫request加上一個header
      request = req.Request(url, headers = {
          "User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:72.0) Gecko/20100101 Firefox/72.0"
      })
      
      with req.urlopen(request) as response:
          data = response.read().decode("utf-8")
      
      import bs4
      root = bs4.BeautifulSoup(data,"html5lib")
      
      # 依序執行丁列程式
      print(type(root)) #取得的資料型態變成是bs4的物件,而非字串
      print(type(root.prettify())) #可以先美化輸出,方便查看HTML結構
      print(root.prettify()[:100])
      
      <class 'bs4.BeautifulSoup'>
      <class 'str'>
      <!DOCTYPE html>
      <html>
       <head>
        <meta charset="utf-8"/>
        <meta content="width=device-width, initia
      
    • BeautifulSoup4 四大物件

      BeautifulSoup4 將複雜 HTML 文件轉換成一個複雜的樹形結構,每個節點都是 Python 物件,所有物件可以歸納為 4 種:

      • Tag: HTML 中的一個個標籤,例如:<title>, <head>, <div>, <link>, <a>…,Tag 下擁有許多屬性和方法,和前端類似,例如 a 標籤一定會有它的 href 屬性,某些屬性是某些標籤所獨有的。
      • NavigableString: 標籤內部的文字: .string
      • BeautifulSoup: BeautifulSoup 物件就是通過解析網頁所得到的物件,我們的 soup 即是 BeautifulSoup 物件. 可以把它當作 Tag 物件,是一個特殊的 Tag,我們可以分別獲取它的型別,名稱,以及屬性
      • Comment: 物件是網頁中的註釋及特殊字串,當你提取網頁中的註釋的時候,它會自動幫你生成 Comment 物件
    • BeautifulSoup的方法44
      方法 說明
      select() 以 CSS 選擇器的方式尋找指定的 tag。
      find_all() 以所在的 tag 位置,尋找內容裡所有指定的 tag。
      find() 以所在的 tag 位置,尋找第一個找到的 tag。
      find_parents() 以所在的 tag 位置,尋找父層所有指定的 tag 或第一個找到的 tag。
      find_parent()  
      find_next_siblings() 以所在的 tag 位置,尋找同一層後方所有指定的 tag 或第一個找到的 tag。
      find_next_sibling()  
      find_previous_siblings() 以所在的 tag 位置,尋找同一層前方所有指定的 tag 或第一個找到的 tag。
      find_previous_sibling()  
      find_all_next() 以所在的 tag 位置,尋找後方內容裡所有指定的 tag 或第一個找到的 tag。
      find_next()  
      find_all_previous() 所在的 tag 位置,尋找前方內容裡所有指定的 tag 或第一個找到的 tag。
      find_previous()  

      下方的程式碼,使用 Beautiful Soup 取得範例網頁中指定 tag 的內容。

    • 第四版:擷取所需資訊
      import urllib.request as req
      url = "https://www.ptt.cc/bbs/Food/index.html"
      # 幫request加上一個header
      request = req.Request(url, headers = {
          "User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:72.0) Gecko/20100101 Firefox/72.0"
      })
      
      with req.urlopen(request) as response:
          data = response.read().decode("utf-8")
      
      import bs4
      root = bs4.BeautifulSoup(data,"html.parser")
      #找到所有class為title的div
      titles = root.find_all("div", class_="title")
      
      for t in titles:
          print(t.a)
      
      
      <a href="/bbs/Food/M.1678360881.A.B45.html">[食記] 桃園聚餐餐廳 霸氣帶骨肉早午餐 甜福號</a>
      <a href="/bbs/Food/M.1678363764.A.1B5.html">[食記] 桃園蘆竹區。日日春土雞城</a>
      <a href="/bbs/Food/M.1678363771.A.C75.html">[食記] 金牌咖哩炒麵 基隆暖暖 香辣鍋氣小卷炒麵</a>
      <a href="/bbs/Food/M.1678366722.A.79F.html">[食記] 宜蘭壯圍 大仁哥蔗香脆皮桶仔雞旗艦店</a>
      <a href="/bbs/Food/M.1678368325.A.03E.html">[食記] 新竹 Luau pizza~爆紅網美餐廳超美炮仗花</a>
      <a href="/bbs/Food/M.1678369306.A.359.html">[食記] 中壢 江家羊肉 二訪</a>
      <a href="/bbs/Food/M.1355673582.A.5F7.html">[公告] Food板 板規 V3.91</a>
      <a href="/bbs/Food/M.1190944426.A.E6C.html">[公告] 發文請在標題加上地區及提供地址電話。^^</a>
      <a href="/bbs/Food/M.1128132666.A.0FD.html">[公告] 文章被刪除者請洽精華區的資源回收桶</a>
      <a href="/bbs/Food/M.1496532469.A.C36.html">[公告] 新增板規22:發文禁附延伸閱讀連結</a>
      
  3. 實作2
    import urllib.request as req
    url = "https://www.ptt.cc/bbs/Tainan/index.html"
    # 幫 request 加上一個 header
    request = req.Request(url, headers = {
        "User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:72.0) Gecko/20100101 Firefox/72.0"
    })
    
    with req.urlopen(request) as response:
        data = response.read().decode("utf-8")
    
    import bs4
    root = bs4.BeautifulSoup(data,"html.parser")
    #print(root.prettify())
    titles = root.find_all("div", class_="title")
    print(titles[3].a['href'])
    print(titles[3].text)
    
    #pubs = root.find_all("li", class_="publish")
    #for pub, title in zip(pubs[:5], titles[:5]):
    #    print(pub.span.string,title.a.string)
    
    
    /bbs/Tainan/M.1678370734.A.97D.html
    
    [交易] 永康 深呼吸 健身房 會籍
    
    

9.6. 實作練習

  • Google 美食達人「壽司羊」,以爬蟲程式列出其部落格(在痞客邦)最近五篇文章的標題以及文章月份。
Jun  【高雄食記】初一車輪燒紅豆餅/只要 25 元就可以吃到一大塊的巧克力戚風蛋糕做成的雙重巧克力車輪餅,會牽絲的起司蛋也很好吃((附菜單
Jun  【高雄美食】長代堂食品行/好吃的長崎蜂蜜蛋糕,底下那一層才是配上無糖紅茶的隱藏版超級美味甜點,千萬不要丟掉了!!!
Jun  【冷凍宅配】蝦大俠室內有機養殖 白蝦 草蝦/不出門簡單煮,在家就可以吃到美味的蝦子,不管是烤蝦,水煮蝦都很方便快速。
Jun  【台北美食】地中海牛排館 歐華酒店/超高 CP 值的用餐優惠方案,濕式熟成 50 天肋眼牛排+1000 元送高級雙人房住宿一晚((附菜單
Jun  【高雄食記】玖雞炸物/堅持使用新鮮雞肉,每天到鳳農市場採購,吃得到雞肉鮮味鹽酥雞小店,墨魚黑輪也很好吃,脫油機不油膩((附菜單

10. GUI/Web-based

10.1. Gradio

讓只有command line I/O的python程式搖身一變成為web service。

  1. 安裝套件
    1: pip install gradio
    
  2. Hello world45
    • fn :被 UI 裝飾的函式
    • inputs :輸入元件。如 “text” 、 “image” 、 “audio” 等
    • outputs :輸出元件。如 “text” 、 “image” 、 “label” 等

    Gradio 支援 20 多種不同的元件型別,其中大部分都可以作為輸入/輸出元件,詳見官網文件gradio Docs

    import gradio as gr
    
    def greet(name):
        return "Hello " + name + "!!"
    
    demo = gr.Interface(fn=greet, inputs="text", outputs="text")
    
    demo.launch()
    
  3. 數字 I/O
    import gradio as gr
    
    def BMI(h, w):
      h /= 100
      return f'BMI值: {w/(h*h)}'
    
    ui = gr.Interface(
        fn=BMI, inputs=[gr.Slider(100, 240, label="身高(cm)"), gr.Slider(40, 200, label="體重(kg)")],
        outputs=["text"]
    )
    ui.launch(share=True)
    
    • 課堂練習

      輸入三角形三邊長(1..100),輸出面積、周長

  4. 文字 I/O
    import gradio as gr
    
    def greet(name):
        return "Hello " + name + "!"
    
    demo = gr.Interface(
        fn=greet,
        inputs=gr.Textbox(lines=2, placeholder="Name Here..."),
        outputs="text",
    )
    demo.launch()
    
  5. 多資料 I/O
    import gradio as gr
    
    def greet(name, is_morning, temperature):
        salutation = "Good morning" if is_morning else "Good evening"
        greeting = f"{salutation} {name}. It is {temperature} degrees today"
        celsius = (temperature - 32) * 5 / 9
        return greeting, round(celsius, 2)
    
    demo = gr.Interface(
        fn=greet,
        inputs=["text", "checkbox", gr.Slider(0, 100)],
        outputs=["text", "number"],
    )
    demo.launch()
    
  6. 圖形 I/O
    import gradio as gr
    import matplotlib.pyplot as plt
    import numpy as np
    def curve(a, b, c):
        x = np.arange(-3, 3, 0.3)
        y = a*x**2 + b*x + c
        fig = plt.figure()
        plt.plot(x, y)
        print(x)
        print(y)
        return fig
    inputs = [gr.Slider(0, 10, 5), gr.Slider(0, 10, 5), gr.Slider(0, 10, 5)]
    outputs = gr.Plot()
    demo = gr.Interface(
        fn=curve,
        inputs=inputs,
        outputs=outputs,
        cache_examples=True,)
    demo.launch()
    
  7. Button
    import gradio as gr
    
    def greet(name):
        return "Hello " + name + "!"
    
    with gr.Blocks() as demo:
        name = gr.Textbox(label="Name")
        output = gr.Textbox(label="Output Box")
        greet_btn = gr.Button("Greet")
        greet_btn.click(fn=greet, inputs=name, outputs=output)
    
    demo.launch()
    
  8. Radio
    import gradio as gr
    
    def greet(gender, name):
        if gender == "Male":
            return "Hello, Mr. " + name + "!"
        else:
            return "Hello, Ms. " + name + "!"
    
    with gr.Blocks() as demo:
        input = [gr.Radio(["Male", "Female"]), gr.Textbox(label="Name)]
        output = gr.Textbox(label="Output Box")
        greet_btn = gr.Button("Greet")
        greet_btn.click(fn=greet, inputs=input, outputs=output)
    
    demo.launch()
    

10.2. Flask

  1. Button
    from flask import Flask
    app = Flask(__name__)
    
    @app.route("/")
    def hello():
        title = "<title>Advanced Materials of Python</title>"
        h1 = "<h1>Python based web</h1>"
        p1 = "<p>這是用Python開發的網站</p>"
        return title+h1+p1
    
    if __name__ == "__main__":
        app.run()
    

    FlashSite.jpg

    Figure 42: Flask Web Site

  2. Input form46

    2023-03-22_09-04-20_2023-03-22_09-01-27 (1).gif

    Figure 43: 標題

    • Python
      # Flask網站前後端互動 09 - 超連結與圖片
      # 載入Flask、Request、render_template
      from flask import Flask, request, render_template
      
      # 建立 Application 物件,設定靜態檔案的路徑處理
      # http://127.0.0.1:5000/head.png 為圖片路徑
      app = Flask(__name__, static_folder="public", static_url_path="/")
      
      # 處理路徑 / 的對應函市
      
      @app.route("/")
      def main():
          return render_template("main.html")
      
      @app.route("/page")
      def page():
          #從main.html頁面中讀取姓名,存在name中
          name = request.args.get("nameis")
          #將name的資料轉給pate.html中的變數namepage
          return render_template("page.html", namepage=name)
      # 啟動Server
      app.run()
      
    • templates
      1. 建立templates資料夾
      2. 以下兩個html檔要放在templates資料夾中
      • index.html
        <!DOCTYPE html>
        <html lang="en">
        
        <head>
            <meta charset="UTF-8">
            <meta http-equiv="X-UA-Compatible" content="IE=edge">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>這是標題</title>
        </head>
        
        <body>
            <h3>網頁的主畫面</h3>
            <form action="/page">
                名字:<input type="text" name="nameis">
                <button>點擊送出</button>
            </form>
        </body>
        
        </html>
        
      • page.html
        <!DOCTYPE html>
        <html lang="en">
        
        <head>
            <meta charset="UTF-8">
            <meta http-equiv="X-UA-Compatible" content="IE=edge">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>page1</title>
        
        </head>
        
        <body>
            <h1>我的名字叫 {{namepage}}</h1>
        </body>
        
        </html>
        

10.3. tkinter

  1. 安裝
    • MacOS

      於PyCharm的terminal下輸入brew install python-tk@Python版本
      如果系統的預設python版本為3.9,則輸入

      brew install python-tk@3.9
      pip install tk
      
  2. DEMO #1
    # -*- coding: utf-8 -*-
    import tkinter as tk
    from tkinter import messagebox
    # 按完button要做什麼
    def test():
        tk.messagebox.showinfo('測試', 'Hi')
    
    # main window
    root = tk.Tk()
    root.title('my window')
    root.geometry('200x150')
    
    root.configure(background='white')
    
    myentry = tk.Entry(root)
    myentry.pack()
    
    # Button
    myButton = tk.Button(root, text='Button', command=test)
    myButton.pack()
    
    # Label
    resultLabel = tk.Label(root, text='這是Label')
    resultLabel.pack()
    
    root.mainloop()
    
  3. DEMO #2
    # -*- coding: utf-8 -*-
    import tkinter as tk
    from tkinter import messagebox
    
    def button_event():
        #print(var.get())
        if var.get() == '':
            tk.messagebox.showerror('message', '未輸入答案')
        elif var.get() == '2':
            tk.messagebox.showinfo('message', '答對了!')
        else:
            tk.messagebox.showerror('message', '答錯')
    
    root = tk.Tk()
    root.title('my window')
    
    # label
    mylabel = tk.Label(root, text='1+1=')
    mylabel.grid(row=0, column=0)
    
    var = tk.StringVar()
    myentry = tk.Entry(root, textvariable=var)
    myentry.grid(row=0, column=1)
    
    mybutton = tk.Button(root, text='完成', command=button_event)
    mybutton.grid(row=1, column=1)
    
    root.mainloop()
    

11. TODO Multiprocessing

12. TODO Interactive Plots in Jupyter Notebook

Footnotes:

Author: Yung Chin, Yen

Created: 2023-04-17 Mon 14:44