『Python数値計算ノート』ではアフィリエイトプログラムを利用して商品を紹介しています。

構造化配列

ある統計データを作成するために、人々の名前、年齢、身長などのデータを収集して管理することを考えます。一番素朴な方法は、それぞれのデータを別々の配列に放り込むことです。
 
 name = np.array([“小春”, “小夜子”, “時雨”])
 age = np.array([20, 25, 23])
 height = np.array([156.3, 159.8, 162.5])
 
しかし、このような形のデータを作ってしまうと、特定の名前でデータを検索したいときに、インデックスを手掛かりに要素を引っ張り出すようなコードを書かなくてはなりません。Python では、この種のデータはディクショナリに格納して管理していましたが、大規模なデータを扱う分析処理においては遅すぎて使い物になりません。

NumPy では相異なるデータ型をもつ配列(フィールド)を組み合わせて1つの配列として扱うことができます。このような配列を 構造化配列 (structured array) とよびます。
 
構造化配列を作成するためには、最初に 複合データ型 を定義しておいて、配列作成時に dtype 引数に渡します。複合データ型を定義する方法は色々ありますが、タプルのリストが一番わかりやすいと思います。以下のサンプルコードを参考にしてください。

# PYTHON_NUMPY_STRUCTURE_01

# In[1]

import numpy as np

# 複合データ型の定義
# U12:最大長12のUnicode文字列
# i1:1バイト整数(=np.int8)
# f2:半精度浮動小数点数(=np.float16)
dtype = [("name", "U12"), ("age", "i1"), ("height", "f2")]

# 構造化配列を作成
data = np.zeros(3, dtype = dtype)

# 構造化配列へのデータの書き込み
data["name"] = ["小春", "小夜子", "時雨"]
data["age"] = [20, 25, 23]
data["height"] = [156.3, 159.8, 162.5]

print(data)
# [('小春', 20, 156.2) ('小夜子', 25, 159.8) ('時雨', 23, 162.5)]

データ型の指定には型コードとよばれる短縮形を用いています。
型コードの最初の文字はデータの種類を表します。
たとえば、i は整数、f は浮動小数点数を表しています。
後ろの数字はバイト数です。1 バイト = 8 ビットなので、たとえば “f2” は 16 ビットの浮動小数点数 (np.float16) を表します。もちろん、短縮形を用いずに指定することもできます。
 
構造化配列を作っておけば、簡単なコードで色々なデータを取り出すことができます。

# In[2]

# 名前の一覧
x = data["name"]

# 身長の一覧
y = data["height"]

# 2人目のデータ
z = data[1]

print("名前", x)
print("身長", y)
print("2人目のデータ", z)

# 名前 ['小春' '小夜子' '時雨']
# 身長 [156.2 159.8 162.5]
# 2人目のデータ ('小夜子', 25, 159.8)

numpy.dtype() を使うと、より複雑な複合データ型をつくることができます。たとえば、次のコードは数値(スカラー)と配列を要素にもつ構造化配列を作成します。

# PYTHON_NUMPY_STRUCTURE_02

# In[1]

import numpy as np

# 複合データ型の定義
# i4:32バイト整数(=np.int32)
# f4:単精度浮動小数点数(=np.float32)
d = np.dtype([("x", "i4"), ("y", "f4", (2, 2))])

# 構造化配列を作成
st_a = np.zeros(2, dtype = d)

print(st_a)
# [(0, [[0., 0.], [0., 0.]]) (0, [[0., 0.], [0., 0.]])]

コメント

  1. HNaito より:

    構造化配列の作成やデータの参照は理解できましたが、データの修正、削除、追加はどうやるのでしょうか。

    • あとりえこばと より:

      記事本文中の In[1] が実行されているとします。以下のコードは、”name” フィールドに直接アクセスしてフィールドを丸ごと書き換えています。

      data["name"] = ['Koharu', 'Sayoko', 'Sigure']
      
      print(data)
      
      # [('Koharu', 20, 156.2) ('Sayoko', 25, 159.8) ('Sigure', 23, 162.5)]


      フィールドの個々の要素にアクセスして変更することもできます。

      data["name"][0] = 'Suzune'
      
      print(data)
      
      # [('Suzune', 20, 156.2) ('Sayoko', 25, 159.8) ('Sigure', 23, 162.5)]


      しかし、フィールドの要素の削除は困難です。
      たとえば、要素を削除する numpy.delete() を使うと …

      np.delete(data["name"], 0)
      
      # array(['Sayoko', 'Sigure'], dtype='U12')


      このように、一見すると一部の要素が削除されたように思えますが、

      print(data)
      
      # [('Suzune', 20, 156.2) ('Sayoko', 25, 159.8) ('Sigure', 23, 162.5)]


      もとの data は変わっていません。
      numpy.delete() はコピーを生成する関数だからです。
      numpy.delete() でタプルごと消したコピーは作成できるので、そのコピーを新しい data として採用すれば、1 セットのデータの削除は可能といえるかもしれません。

      data = np.delete(data, 0)
      
      print(data)
      
      # [('Sayoko', 25, 159.8) ('Sigure', 23, 162.5)]


      最後にデータの追加です。以下のように、別の構造化配列を作成したとします。

      data_2 = np.zeros(2, dtype = dtype)
      
      data_2["name"] = ["Kotaro", "Masayuki"]
      data_2['age'] = [16, 42]
      data_2['height'] = [169.5, 172.0]
      
      print(data_2)
      
      # [('Kotaro', 16, 169.5) ('Masayuki', 42, 172. )]


      numpy.append() で data と data_2 を結合できます。

      data_3 = np.append(data, data_2)
      
      print(data_3)
      
      # [('Sayoko', 25, 159.8) ('Sigure', 23, 162.5) ('Kotaro', 16, 169.5)
       ('Masayuki', 42, 172. )]


      ただし、これもコピーです。

      • HNaito より:

        丁寧にご回答いただきありがとうございました。
        delete と append については自動でバックアップを残してくれると考えておくことにします。
        ところで、print(data.dtype) を実行すると、
        [(‘name’, ‘<U12'), ('age', 'i1'), ('height', '<f2')]
        と表示されるのですが、U12 と f2 の前の < は何を意味するのでしょうか。