一. 為什么需要結(jié)構(gòu)數(shù)組
數(shù)據(jù)分析過程中,經(jīng)常會有多種不同數(shù)據(jù)類型同時出現(xiàn),而不僅僅是期望的數(shù)值型數(shù)據(jù),而Array只能含有一種數(shù)據(jù)類型,Numpy/pandas該如何處理呢?在C語言中經(jīng)常通過結(jié)構(gòu)體struct來定義不同數(shù)據(jù)類型形成結(jié)構(gòu)類型,結(jié)構(gòu)中的字段占據(jù)連續(xù)的內(nèi)存空間,每個結(jié)構(gòu)體占用的內(nèi)存大小均相同,類似的Numpy可以很容易的定義結(jié)構(gòu)數(shù)組。和C語言一樣,在Numpy中也可以操作這些字段對這種結(jié)構(gòu)數(shù)組進(jìn)行操作。只要Numpy的結(jié)構(gòu)和C語言中的定義相同,Numpy就可以很方便地讀取C語言的結(jié)構(gòu)數(shù)組的二進(jìn)制數(shù)據(jù),轉(zhuǎn)換為Numpy的結(jié)構(gòu)數(shù)組。
例如,我們定義一個結(jié)構(gòu)數(shù)組,它的每個元素有name,age和weight字段。在Numpy中可以如下定義:
>>>import numpy as np
>>>## 數(shù)據(jù)類型升級為string,其他類似C語言數(shù)據(jù)類型轉(zhuǎn)換規(guī)則, 不會出現(xiàn)多種數(shù)據(jù)類型
>>>np.array([1,2,'a'])
array(['1', '2', 'a'], dtype='|S21')
二. 定義結(jié)構(gòu)數(shù)組
現(xiàn)在我們定義一個結(jié)構(gòu)數(shù)組,它的每個元素有name,age和weight字段。在Numpy中可以如下定義:
>>>persontype = np.dtype({
"names":["name", "height", "weight"],
"formats":["S32", "i", "f"]
})
>>>p = np.array([("Lee", 180, 74.5), ("Zhang", 170, 55)],
dtype=persontype)
我們創(chuàng)建一個dtype對象persontype,通過其字典參數(shù)描述結(jié)構(gòu)類型的各個字段。字典有兩個關(guān)鍵字:names、formats. 每個關(guān)鍵字對應(yīng)的值都是一個列表, names定義結(jié)構(gòu)中的每個字段名,formats則定義每個字段的數(shù)據(jù)類型:
- S32: 32個字節(jié)的字符串類型, 由于結(jié)構(gòu)中每個元素的大小必須固定, 因此需要指定字符串的長度
- i: 32bit的整數(shù)類型, 相當(dāng)于np.int32
- f: 32bit的單精度浮點數(shù), 相當(dāng)于np.float32
然后我們調(diào)用array函數(shù)創(chuàng)建數(shù)組, 通過關(guān)鍵字參數(shù)dtype=persontype, 指定所創(chuàng)建的數(shù)組的元素類型維結(jié)構(gòu)數(shù)組persontype. 可以看到數(shù)組p的元素類型:
>>>p.dtype
dtype([('name', 'S32'), ('height', '<i4'), ('weight', '<f4')])
這里我們看到了另一種描述結(jié)構(gòu)類型的方法: 一個包含多個組元的列表,其中如 (字段名, 類型描述) 的組元來描述結(jié)構(gòu)數(shù)組中的每個字段. 類型描述前面的"|","<",">"描述了字段值的字節(jié)順序:
- |: 忽視字節(jié)順序
- <: 低位字節(jié)在前, 即大段序
- >: 高位字節(jié)在前, 即小端序
2.1 四種定義方法
結(jié)構(gòu)數(shù)組有四種定義方式,即表述dtype對象結(jié)構(gòu)的參數(shù)方式:
- string
- tuple
- list
- dict
上面例子即為dict定義的dtype結(jié)構(gòu)類型。
2.1.1 string參數(shù)
dtype類型用一個逗號分割數(shù)據(jù)類型的string,每個類型對一個的數(shù)據(jù)采用默認(rèn)名字‘f0’、‘f1’、‘f2’...,數(shù)據(jù)類型有四種形式:
- a) b1, i1, i2, i4, i8, u1, u2, u4, u8, f2, f4, f8, c8, c16, a<n>
(分別對應(yīng) bytes, ints, unsigned ints, floats, complex and
fixed length strings of specified byte lengths-固定字節(jié)長度的字符串) - b) int8,...,uint8,...,float16, float32, float64, complex64, complex128 (這里是按位長計算bit sizes)
此外還有 Numerric/numarray類型(如Float32)和單字符類型(如H代表usigned short ints),但已棄用,就不建議使用了。
>>>x = np.zeros(3, dtype='3int, float32, (2,3)float64')
>>>x
array([([0, 0, 0], 0., [[ 0., 0., 0.], [ 0., 0., 0.]]),
([0, 0, 0], 0., [[ 0., 0., 0.], [ 0., 0., 0.]]),
([0, 0, 0], 0., [[ 0., 0., 0.], [ 0., 0., 0.]])],
dtype=[('f0', '<i8', (3,)), ('f1', '<f4'), ('f2', '<f8', (2, 3))])
2.1.2 tuple參數(shù)
元組僅適用于結(jié)構(gòu)的數(shù)據(jù)和現(xiàn)有的數(shù)據(jù)類型對應(yīng)的結(jié)構(gòu)數(shù)組,呈元組對出現(xiàn).
>>>x = np.zeros(3, dtype=('i4', [('r','u1'), ('g','u1'), ('b','u1'), ('a','u1')]))
>>>x['r']
array([0, 0, 0], dtype=uint8)
2.1.3 list參數(shù)
dtype由一組tuple的list定義,每個元組包含2-3個元素:1)數(shù)據(jù)結(jié)構(gòu)域的名字,2)對應(yīng)的數(shù)據(jù)類型,3)數(shù)據(jù)域的shape(可選).
>>>x = np.zeros(3, dtype=[('x', 'f4'), ('y', np.float32), ('value', 'f4', (2,2))])
>>>x
array([( 0., 0., [[ 0., 0.], [ 0., 0.]]),
( 0., 0., [[ 0., 0.], [ 0., 0.]]),
( 0., 0., [[ 0., 0.], [ 0., 0.]])],
dtype=[('x', '<f4'), ('y', '<f4'), ('value', '<f4', (2, 2))])
2.1.4 dict參數(shù)
有兩種不同的形式,一種字典是必須含'names'和'formats'關(guān)鍵字,對應(yīng)一個相同長度的list,其中'names'必須是字符串. 另有兩個可選關(guān)鍵字'offsets'和'titles', 'offsets'對應(yīng)的list是每個數(shù)據(jù)域字節(jié)為單位偏移量, 'titles'對應(yīng)的list是數(shù)據(jù)域元數(shù)據(jù)的對象. 開始的例子便是此種類型.
>>>x1 = np.zeros(3, dtype={'names':['col1', 'col2'], 'formats':['i4', 'f4']})
>>>x1
array([(0, 0.), (0, 0.), (0, 0.)],
dtype=[('col1', '<i4'), ('col2', '<f4')])
另一種字典是以數(shù)據(jù)域name做keys, 對應(yīng)的value為含有(type,offset,title)的tuple, 其中title可選. 由于dict是沒有順序的, 每個數(shù)據(jù)域的順序需要在定義時給出, 這個即是offset. 如height字段的偏移量為25個字節(jié):
>>>np.dtype({"name":("S25", 0), "age":(np.uint8, 25)})
dtype([('name', 'S25'), ('age', 'u1')])
>>>x1 = np.zeros(3, dtype={'col1':('i1',0,'title 1'), 'col2':('f4',1,'title 2')})
>>>x1
array([(0, 0.), (0, 0.), (0, 0.)],
dtype=[(('title 1', 'col1'), 'i1'), (('title 2', 'col2'), '<f4')])
三. 混合結(jié)構(gòu)數(shù)組
結(jié)構(gòu)類型中可以包括其他結(jié)構(gòu)類型, 下面創(chuàng)建一個有一個字段f1的結(jié)構(gòu), f1的值是另外一個結(jié)構(gòu)f2, 其類型為16bit的整數(shù).
>>>np.dtype([("f1", [("f2", np.int16)])])
dtype([('f1', [('f2', '<i2')])])
當(dāng)某個字段類型為數(shù)組時, 用組元的第三個參數(shù)表示, 下面描述的f1字段是一個shape為(2, 3)的雙精度浮點數(shù)組:
>>>np.dtype([("f0", "i4"), ("f1", "f8", (2, 3))])
dtype([('f0', '<i4'), ('f1', '<f8', (2, 3))])
四. 結(jié)構(gòu)數(shù)組的存取
結(jié)構(gòu)數(shù)組的存取和一般array相同, 通過下標(biāo)即可, 需要注意的是元素的值雖然看著像組元, 但實際上是個結(jié)構(gòu)數(shù)組:
>>>>p[0], p[0].dtype.names
(('Lee', 180, 74.5), ('name', 'height', 'weight'))
>>>p[0].dtype
dtype([('name', 'S32'), ('height', '<i4'), ('weight', '<f4')])
p[0]是一個結(jié)構(gòu)元素, 它和a共享內(nèi)存數(shù)據(jù), 因而可以通過修改元素字段來改變原始結(jié)構(gòu)數(shù)組中的對應(yīng)字段:
>>>c = p[1]
>>>c["name"] = "Li"
>>>p[1].dtype
dtype([('name', 'S32'), ('height', '<i4'), ('weight', '<f4')])
結(jié)構(gòu)數(shù)組可以像dict一樣通過下標(biāo)獲取對應(yīng)的字段值:
>>>b = p[:]["height"]
>>>b
array([180, 170], dtype=int32)
>>>b[0] = 200
>>>p[0]["height"]
200
同樣的, 結(jié)構(gòu)數(shù)組也可以調(diào)用tostring()和tofile()方法, 直接輸出數(shù)組p的二進(jìn)制形式:
p.tofile("test.bin")
利用下面的C語言可以將test.bin文件中的數(shù)據(jù)讀出來.
C語言的結(jié)構(gòu)體為了內(nèi)存尋址方便, 會自動對較短字段填充一些字節(jié), 稱為內(nèi)存對齊. 如果把下面的name[32]改為name[30], 由于內(nèi)存對齊的原因, 在name和height中間會填補兩個字節(jié), 結(jié)構(gòu)體大小并不改變. 因此如果numpy中鎖配置的內(nèi)存大小不符合C語言的對齊規(guī)范, 將會出現(xiàn)數(shù)據(jù)錯位. 為了解決這個問題, 在創(chuàng)建dtype對象時, 可以傳遞參數(shù)align=True, 這樣numpy的結(jié)構(gòu)數(shù)組就和C語言的結(jié)構(gòu)體的內(nèi)存對齊是一致的.
#include <stdio.h>
struct person{
char name[32];
int height;
float weight;
};
struct person p[2];
void main(){
FILE *fp;
int i;
fp = fopen("test.bin", "rb");
fread(p, sizeof(struct person), 2, fp);
fclose(fp);
for(i=0; i<2; i++)
printf("%s %d %f\n", p[i].name, p[i].weight);
getchar();
}
雖然上面羅嗦了這么多, 然而并沒什么卵用, 對這類數(shù)據(jù), 我們當(dāng)然是交給將Numpy封裝的更好的Pandas來處理了_