Data Types for Data Science in Python 是 DataCamp 中 Python Programmer Career Track 的第二门课程。

本节课可以学习到:

  • 基础数据类型的运用,包括列表、字典、元组等
  • 对于 collections 模块中进阶数据类型的使用
  • 通过 datetime 及第三方的 pytz 与 pendulum 等库处理日期、时间与时区转换

基础数据类型

List

  • 按照添加顺序维护数据
  • Mutable(可变的)
  • 支持索引
  • 使用加法运算符可以合并列表
  • 使用 .extend() 方法追加参数中的列表至实例自身(self)的末尾
  • 使用 .index() 方法定位列表中数据的索引位置
  • 使用 .pop() 移除并返回指定位置上的元素
  • 使用 for in 循环来迭代列表元素
  • 使用 .sorted() 方法返回新的排序后列表
cookies = ['chocolate chip', 'peanut butter', 'oatmeal', 'sugar']
cookies.append('Tirggel')
print(cookies[2])        # oatmeal

cakes = ['strawberry', 'vanilla']
desserts = cookies + cakes
print(desserts)
# ['chocolate chip', 'peanut butter', 'oatmeal', 'sugar', 'Tirggel','strawberry', 'vanilla']

position = cookies.index('sugar')    # 3
name = cookies.pop(position)   # name = 'sugar'
for cookie in cookies:
    print(cookie)
sorted_cookies = sorted(cookies)

Tuple

  • 按顺序维护数据
  • 可索引
  • Immutable(不可变的)
  • Pairing(成对的)
  • Unpackable(可解包的)
  • 使用 zip() 函数将多个列表纵向缝合为元素列表
  • 通过多变量赋值解包元组
  • 使用 enumerate() 函数将可遍历的数据对象组合为 (下标, 数据) 元组的索引序列
  • 使用 () 或末尾多余逗号形成元素(后者支持单元素元组)
in_cookies = ['Punjabi', 'Fruit Cake Rusk']
us_cookies = ['Chocolate Chip', 'Brownies']
top_pairs = zip(us_cookies, in_cookies)
# [('Chocolate Chip', 'Punjabi'), ('Brownies', 'Fruit Cake Rusk')]

us_num_1, in_num_1 = top_pairs[0]
# us_num_1 = 'Chocolate Chip', in_num_1 = 'Punjabi'

for us_cookie, in_cookie in top_pairs:
    print(in_cookie)
    print(us_cookie)

for idx, item in enumerate(top_pairs):
    us_cookie, in_cookie = item
    print(idx, us_cookie, in_cookie)
# (0, 'Chocolate Chip', 'Punjabi')
# (1, 'Brownies', 'Fruit Cake Rusk')

item = ('vanilla', 'chocolate')
item2 = 'butter',
# item2 = ('butter',)

Set

  • 元素的唯一标识
  • 无序的
  • 可变的
  • 数学中集合论的 Python 实现
  • 使用 set(iterable) 构造器从列表生成集合
  • 使用 .add() 方法添加单个元素
  • 使用 .update() 方法融合其他列表或集合
  • 使用 .discard() 移除指定内容元素
  • 使用 .pop() 随机移除元素(实际上是移除哈希序最左元素,当集合为空时会有 KeyError
  • 使用 .union() 返回两个集合的交集
  • 使用 .intersection() 返回两个集合的并集
  • 使用 .intersection() 返回两个集合的差集(self – arg)
cookies_eaten_today = ['chocolate chip', 'peanut butter', 'chocolate chip', 'oatmeal cream', 'chocolate chip']
types_of_cookies_eaten = set(cookies_eaten_today)
# types_of_cookies_eaten = set(['chocolate chip', 'oatmeal cream', 'peanut butter'])

types_of_cookies_eaten.add('biscotti')
cookies_hugo_ate = ['chocolate chip', 'anzac']
types_of_cookies_eaten.update(cookies_hugo_ate)

types_of_cookies_eaten.discard('biscotti')
types_of_cookies_eaten.pop()

cookies_jason_ate = set(['chocolate chip', 'oatmeal cream', 'peanut butter'])
cookies_hugo_ate = set(['chocolate chip', 'anzac'])
cookies_jason_ate.union(cookies_hugo_ate)
# set(['chocolate chip', 'anzac', 'oatmeal cream', 'peanut butter'])
cookies_jason_ate.intersection(cookies_hugo_ate)
# set(['chocolate chip'])
cookies_jason_ate.difference(cookies_hugo_ate)
# set(['oatmeal cream', 'peanut butter'])
cookies_hugo_ate.difference(cookies_jason_ate)
# set(['anzac'])

Dictionary

  • 以键/值对的方式维护数据
  • Nestable(可嵌套的)
  • Iterable(可迭代的)
  • 使用 dict() 或 {} 生成字典
  • 使用键作为索引获取对应值
  • 当试图访问的键不存在时,会抛出 KeyError
  • 使用 .get() 安全地访问以避免异常,不存在时返回 None 或指定返回内容参数
  • 使用 .keys() 方法得到 dict_keys 类型的类数组对象
  • 使用 .update() 方法从其他字典或元组中更新值到指定键
  • 使用 del 删除指定键值对引用(对象后续被 GC 自动回收)
  • 使用 .pop() 方法移除指定键值对
  • 使用 .items() 方法返回可迭代的 dict_items 类型对象
  • 使用 in 来判断字典中是否有该键存在
art_galleries = {}
for name, zip_code in galleries:
    art_galleries[name] = zip_code
for name in art_galleries:
    print(name)

art_galleries.get('Louvre')
# None
art_galleries.get('Louvre', 'Not Found')
# 'Not Found'

art_galleries.keys()
# dict_keys(['10021', '10013', '10001', '10009', '10011', '10022',   ...: '10027', '10019', '11106', '10128'])
galleries_11234 = [('A J ARTS LTD', '(718) 763-5473'), ('Doug Meyer Fine Art', '(718) 375-8006'), ('Portrait Gallery', '(718) 377-8762')]
# Tuple into dictionary value
art_galleries['11234'].update(galleries_11234)
print(art_galleries['11234'])
# {'Portrait Gallery': '(718) 377-8762', 'A J ARTS LTD': '(718) 763-5473', 'Doug Meyer Fine Art': '(718) 375-8006'}

del art_galleries['11234']
galleries_10310 = art_galleries.pop('10310')

for key, value in art_galleries.items(): 
    print(key)
    print(value)

'11234' in art_galleries
# False
if'10010'in art_galleries:
    print('I found: %s' % art_galleries['10010'])
else:
    print('No galleries found.')
# I found: {'Nyabinghi Africian Gift Shop': '(212) 566-3336'}

CSV

  • 一些 CSV 的第一行有表头
  • 需要引入 Python 的 csv 模块
  • open() 函数可以指定打开模式(’r’ 或 ‘w’)返回可访问的 csv 变量
  • csv.reader() 函数接受 csv 变量,返回逐行列表
  • .close() 方法关闭 csv 变量
  • csv.DictReader() 函数接受 csv 变量,返回逐行字典,键为表头值为该行内容
# NAME,TEL,ADDRESS1,ADDRESS2,CITY,ZIP (表头)
# O'reilly William & Co Ltd,(212) 396-1822,52 E 76th St,,New York,10021 (正文)

import csv
csvfile = open('ART_GALLERY.csv', 'r')
for row in csv.reader(csvfile):
    print(row)
# ['NAME', 'the_geom', 'TEL', 'URL', 'ADDRESS1', 'ADDRESS2', 'CITY', 'ZIP']
# ["O'reilly William & Co Ltd",'POINT (-73.96273074561996 40.773800871637576)'(212) 396-1822', '52 E 76th St', '', 'New York', '10021']
csvfile.close()

csvfile = open('ART_GALLERY.csv', 'r')
for row in csv.DictReader(csvfile):
    print(row)
# OrderedDict([('NAME', 'Odyssia Gallery'),
# ('the_geom', 'POINT (-73.96269813635554 40.7618747512849)'
# ('TEL', '(212) 486-7338'),
# ('URL', 'http://www.livevillage.com/newyork/art/odyssia-gallery.html'),
# ('ADDRESS1', '305 E 61st St'),
# ('ADDRESS2', ''),('CITY', 'New York'), ('ZIP', '10021')])
csvfile.close()

collections 模块

简介

  • 标准库的一部分
  • 提供了进阶的数据容器

Counter

  • 使用 Counter() 构造器生成空白 Counter 对象或指定可迭代对象、字典对象以生成 Counter 对象
  • Counter 的实质:用于计数的特殊字典 {key: 出现次数}
  • 使用 .most_common(n) 方法获取前 n 个最频繁元素及次数的元组
from collections import Counter
nyc_eatery_count_by_types = Counter(nyc_eatery_types)
print(nyc_eatery_count_by_type)
# Counter({'Mobile Food Truck': 114, 'Food Cart': 74, 'Snack Bar': 24,'Specialty Cart': 18, 'Restaurant': 15, 'Fruit & Vegetable Cart': 4})

print(nyc_eatery_count_by_types.most_common(3))
# [('Mobile Food Truck', 114), ('Food Cart', 74), ('Snack Bar', 24)]

defaultdict

  • 使用 defaultdict(type) 来生成默认为 type 类型的字典
  • 当访问的键不存在时,默认使用该类型的初始化方案
from collections import defaultdict
eatery_contact_types = defaultdict(int)
for eatery in nyc_eateries:
    if eatery.get('phone'):
        eatery_contact_types['phones'] += 1
    if eatery.get('website'):
        eatery_contact_types['websites'] += 1

OrderedDict

  • Python 3.6 前字典无序,3.6 后默认字典是有序的
  • 使用 .popitem() 方法弹出并返回最后一个元素(最晚插入的)
  • 使用 .popitem(last=False) 参数弹出并返回第一个元素(最早插入的)
from collections import OrderedDict
nyc_eatery_permits = OrderedDict()
print(list(nyc_eatery_permits.items())[:3]
print(nyc_eatery_permits.popitem())
print(nyc_eatery_permits.popitem(last=False))

namedtuple

  • 每个特定列都有自己的名字
  • 可以作为 pandas 中 DataFrame 的替代
  • 使用 namedtuple(‘tuplename’, [fieldsname]) 来返回一个像类的 namedtuple 对象
  • 可以通过点操作符访问特定域的值
from collections import namedtuple
Eatery = namedtuple('Eatery', ['name', 'location', 'park_id', 'type_name'])
eateries = []
details = Eatery(eatery['a'], eatery['b'], eatery['c'], eatery['d'])
eateries.append(details)
print(eateries[0])
# Eatery(name='Mapes Avenue Ballfields Mobile Food Truck', location='Prospect Avenue, E. 181st Street', park_id='X289', type_name='Mobile Food Truck')
print(eateries[0].name)
print(eateries[0].park_id)
print(eateries[0].location)

日期、时间与时区

datetime

  • Python 标准库中的模块
  • datetime 模块中提供 datetime 类型(从 datetime 导入 datetime)
  • 使用 datetime.strptme() 函数将字符串 parse 为 datetime 对象
  • %d 充零二位日、%m 充零二位月、%Y 四位年
  • 具体规格化约定见 Python documentation
  • 使用 .strptme(format) 方法将 datetime 对象按照 format 规格字符串约定转换为字符串
  • 使用 .isoformat() 方法将 datetime 对象转换为 ISO 标准字符串
  • 使用点运算符访问 datetime 对象中 day, month, year, hour, minute, second 等成员
  • 使用 datetime.now() 函数获得当前本地日期时间对象
  • 使用 datetime.now() 函数获得当前 UTC 日期时间对象
from datetime import datetime
parking_violations_date = '06/11/2016'
date_dt = datetime.strptime(parking_violations_date, '%m/%d/%Y')
print(date_dt)    # 2016-06-11 00:00:00

date_dt.strftime('%m/%d/%Y')    # '06/11/2016'
date_dt.isoformat()    # '2016-06-11T00:00:00'

print(date_dt.day)    # 11
print(datetime.now())
print(datetime.utcnow())

时区 与 pytz

  • Naive datetime 对象没有时区数据
  • Aware datetime 对象包含时区数据与夏令时等
  • 使用 pytz 模块向 datetime 对象中指定时区数据
  • 使用 .astimezone() 方法进行便捷的时区转换
  • 注:目前似乎正在转为使用 dateutil,pytz 的流行度有待进一步考察
from pytz import timezone
record_dt = datetime.strptime('07/12/2016 04:39PM', '%m/%d/%Y %H:%M%p')

ny_tz = timezone('US/Eastern')
la_tz = timezone('US/Pacific')

ny_dt = record_dt.replace(tzinfo=ny_tz)
la_dt = ny_dt.astimezone(la_tz)

print(ny_dt)    # 2016-07-12 04:39:00-04:00
print(la_dt)    # 2016-07-12 01:39:00-07:00

timedelta

  • timedelta 用来表示时间差
  • 可以与一个 datetime 对象进行加减运算
  • 将两个 datetime 对象相减得到 timedelta 对象作为时间差
from datetime import timedelta
flashback = timedelta(days=90)
print(record_dt)    # 2016-07-12 04:39:00
print(record_dt - flashback)    # 2016-04-13 04:39:00
print(record_dt + flashback)    # 2016-10-10 04:39:00

time_diff = record_dt - record2_dt
type(time_diff)    # datetime.timedelta
print(time_diff)    # 0:00:04

pendulum 库

  • Pendulum Documentation
  • 使用 pendulum.parse() 函数将字符串 parse 为 pendulum datetime 对象,使用 tz= 参数指定时区,当字符串不严格按照标准时使用 strict=False 参数 fall back 至 dateutil 的 parser
  • 使用 .in_timezone(tz) 方法将对象转换为时区 tz
  • 使用 pendulum.now(tz) 获取当前 tz 时区的时间
  • 使用减法得到类似 timedelta 的 pendulum 类型 Period
  • 使用 .diff() 方法得到 Period
  • 使用 .in_days(), .in_hours() 等等或 .in_words() 方法将 Period 以相应月、日、时的整型格式或恰当的文本表示
import pendulum
occurred_dt = pendulum.parse(occurred, tz='US/Eastern')    # <Pendulum [2016-06-11T14:38:00-04:00]>
print(violation_dt.in_timezone('Asia/Tokyo'))    # 2016-06-12T03:38:00+09:00
print(pendulum.now('Asia/Tokyo'))    # <Pendulum [2017-05-06T08:20:40.104160+09:00]>

diff = violation_dts[3] - violation_dts[2]
<Period [2016-04-26T07:09:00-04:00 -> 2016-04-23T07:49:00-04:00]>
print(diff.in_words())    # 2 days 23 hours 20 minutes
print(diff.in_days())    # 2
# diff 计算是考虑时区的