[Power BI] Power Queryの『型』を探る (1) number

（この記事は2022年9月17日の「[PBIJP Power Query 秘密特訓「虎の穴」炎の復活編 #18](https://powerbi.connpass.com/event/258826/)」で使用したものです）

　Power Queryにはプリミティブ型(primitive-type)という基本の型があります。これは、プリミティブ値 (binary、date、datetime、datetimezone、duration、list、logical、null、number、record、text、time、type) を分類するものであり、また、これにはさまざまな抽象型 (function、table、any、none) が含まれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/5712a27e-b259-53f4-c171-ed5605a13811.png)

https://docs.microsoft.com/ja-jp/powerquery-m/m-spec-types

# 1. numberの基本

https://bengribaudo.com/blog/2018/07/31/4497/power-query-m-primer-part7-types-numbers

 値の記述方法は以下のようになります。

```text:表記方法
3.14  // Fractional number 
.5    // Fractional number 
-1.5  // Fractional number 
1.0e3 // Fractional number with exponent
.2e25 // Fractional number with exponent
123   // Whole number 
1e3   // Whole number with exponent 
0xff  // Whole number in hex (255)
```

```text:0除算の表記
#infinity  // produced by an expression like 1/0
-#infinity // produced by an expression like -1/0
#nan       // produced by an expression like 0/0
```
　0で除算を行った場合、Power Queryは **#infinity** を返します。また0を0で除算したり #infinity を #infinityで除算した場合は **#nan** (NaN 非数 Not a Number）を返します。これはエラーではありません。Power Queryの特別な値です。

# 2. numberは倍精度浮動小数点
　Power Queryで扱う数値型は、常に倍精度浮動小数点が用いられます。
　したがって、他のコンピューター計算と同様に小数点の計算で誤差が発生します。
```text:小数点計算での誤差
0.1 + 0.2                 // 0.30000000000000004
10000000000000000 + 1     // 10000000000000000
0.10000000000000001 = 0.1 // true - 倍精度での比較
10000000000000000 = 10000000000000001 // true - 倍精度での比較
```

　以下のように10進数型(decimal)の精度で計算するように明示すると、内部で10進数型で計算が行わるため、誤差は発生しません。
```text:10進数型
Value.Add(10000000000000000, 1, Precision.Decimal)          // 10000000000000001
Value.Compare(0.10000000000000001 , 0.1, Precision.Decimal) // false
Value.Compare(10000000000000000 , 10000000000000001, Precision.Decimal)  // false
```

　計算にかかるシステムのコストよりも正確さが最優先されるような場合は、Value関数を使用する必要があります。

# 3. type claim (型要求)

https://bengribaudo.com/blog/2020/02/28/5009/power-query-m-primer-part17-type-system-ii-facets

　Powre Queryでテーブルを作成するとき、列の型として使えるnubmer型のサブタイプに以下のものがあります。

|アイコン|サブタイプ名|TypeName|プリミティブ型名|
|:-:|:--|:--|:--|
|<img width="40" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/1da3d6f0-ca7e-2b44-fbdd-bb40c38b2da6.png"/>|10進数|Number Type|number|
|<img width="40" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/49a9e901-72d9-353a-3005-577aab3bc80d.png"/>|固定小数点|Currency.Type|number|
|<img width="40" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/ebfffcd3-fc46-208a-378c-ee47f73cb0c6.png"/>|整数|Int64.Type|number|
|<img width="40" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/eb5928cc-d11e-4e78-deb9-bf96ded4484d.png"/>|パーセンテージ|Percentage Type|number|

　また、それ以外にも整数型は Int8.Type, Int16.Type, Int32.Type 、符号なし8ビット整数の Byte.Type があります。また、10進数型は、Single.Type, Double.Type や、固定小数点数型は Decimal.Type があります。
　これらは、マッシュアップエンジンの観点からはnumber型であり、.Typeで表される型はプリセットされたtype claimです。しかし、外部に渡されたデータがこの型情報を使用して効率的な計算や保存ができるようになる可能性があります。

## ファセット (facets)

　Power Queryで扱う型には、ファセットと呼ばれる情報を付与することができます。ファセットはあくまで付加情報なので、動作に何ら影響を与えることはなく、使う必要はほとんどないでしょう。もし、開発者が異なった型をもつ外部システムとデータとのやり取りをする場面で、注意深く作業を行う場合は役に立つかもしれません。

```text:facets
let
    TypeWithFacets =                // テキスト型に対してファセットを３つ設定
        Type.ReplaceFacets(
            type text, 
            [
                MaxLength = 25,
                IsVariableLength = true,
                NativeTypeName = "NVARCHAR"
            ]
        ),

    Source = 
        Table.FromRecords(
            {[Txt = "Hello World!"]},
            type table[
                Txt = TypeWithFacets  // ファセットを設定した型を適用
            ]
        )
in
    Source
```
　このファセットは以下のような設定になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/85b57786-7959-3f7e-e972-5089ca81a20a.png)

　また、Table.Schemaで表示させると以下のようになります。

![名称未設定.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/6118baf0-bd35-9467-0fc6-0a56900c8391.png)

　このファセットの内容は、Power Queryエディッター、Power BI Desktop上で何ら影響がありません。たとえ、MaxLengthが5となっていても"Hello World!"の12文字は切り詰められることはありません。

## 型の変換
　Power Queryでは、数字にはnumber型、文字列にはtext型のように、最初にプリミティブ型が適用され、列の型は **any** が適用されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/12c5a2e9-bbf1-8fb1-1492-ea229ab59356.png)
　この列に対して型を設定すると、それはファセットの1種であるtype claimとして登録されます。あくまでファセットと同様にデータに対しての操作は発生しません。

```text:TypeClaim
let
    Source = 
        Table.FromRecords(
            {
                [Value = 1.23],
                [Value = "Hello World!"],
                [Value = true]
            },
            type table [Value = Number.Type]
        )
in
    Source
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/0897a2b1-cae4-182f-0929-2b0e7753f428.png)

　列の型はNumber.Typeになっているのですが、文字列や論理型の値も表示されています。
　ところが、Table.TransformColumnTypesで型変換を行うと、それぞれの項目に応じた変換作業が行われます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/8beaa348-201a-1dcb-741b-d3a484046fc6.png)

```text:ChangeType
let
    Source = 
        Table.FromRecords(
            {
                [Value = 1.23],
                [Value = "Hello World!"],
                [Value = true]
            },
            type table [Value = Number.Type]
        ),
    ChangedType = 
        Table.TransformColumnTypes(
            Source,
            {
                {"Value", Int64.Type}
            }
        )
in
    ChangedType
```
　Table.TransformColumnTypesは、type claimを設定したあと、その型への変換作業を行います。以下のような変換関数が呼び出され、変換できないものについてはエラーになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/4fb97c3c-ec56-e5e6-07f9-163446f8d043.png)
|値|base type|変換関数|結果|
|:-:|:-:|:--|:--|
|1.23|number|Int64.From(1.23)|1|
|Hello|text|Int64.From("Hello World!")|DataFormat.Error: Number に変換できませんでした。|
|true|logical|Int64.From(true)|1|

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/35773d7e-05bb-47e3-ffc7-7a666dfd225b.png)

　しかし、整数型(Int64.Type)に変換された列の値を操作すると、列の型はふたたび **any** になってしまいます。

```text:ChangeValue
let
    Source = 
        Table.FromRecords(
            {
                [Value = 1.23]
            },
            type table [Value = Number.Type]
        ),
    ChangedType =
        Table.TransformColumnTypes(
            Source,
            {
                {"Value", Int64.Type}   // 整数型に変換
            }
        ),
    ChangedValue =
        Table.TransformColumns(
            ChangedType,
            {
                {"Value", each _ + 1}  // 値に１加えると列の型はany型に変わる
            }
        )
in
    ChangedValue
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/ced2fe80-ba15-ca26-ed21-971849158457.png)

# 4. 型の検出

https://docs.microsoft.com/ja-jp/power-query/data-types#data-type-detection

　データベースなどの構造化データソースに接続するとき、テーブルスキーマを読み取って自動的に表示されます。
　Excel、CSV、テキストファイルなどの非構造化ソースでは、オプションの設定（下記参照）により、テーブル内の上位1000行の値を調べてデータ型が自動的に検出されます。
　また、「変換」タブの「データ型の検出」コマンドを使用して、テーブル内の列のデータ型を自動的に検出することもできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/5fdd72e9-8108-a4be-07ef-407c73c150f0.png)
```text:データ型の検出
Source = Table.FromRecords(
    {[
        Col1 = 3.14,    // type number
        Col2 = .5,      // type number
        Col3 = -1.5,    // type number
        Col4 = 1.0e3,   // Int64.Type
        Col5 = .2e25,   // type number
        Col6 = 123,     // Int64.Type
        Col7 = 1e3,     // Int64.Type
        Col8 = 0xff,    // Int64.Type
        Col9 = #infinity,   // type number
        Col10 = #nan    // type number
    ]}
)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/f2d75c17-09d7-02eb-420d-0cfcc465389c.png)
 小数点が含まれない場合は整数型（Int64.Type）となり、それ以外は全てnumber型となります。

## オプション
　Power BI Desktopのオプションでデータの読み込み時にソースの型の検出についての設定ができます。（Power Query Onlineにも設定があります）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/c2c16901-ee99-50fd-0256-319dd57811c4.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/d7514964-8ebd-5067-9aec-bfedd0b52be2.png)

# 5. クエリエラーと丸め処理
　列の型と値の型が異なっていても、Power Queryエディター上ではエラーになりませんでした。

```text:TypeClaim2
let
    Source = 
        Table.FromRecords(
            {
                [No = 1, Value = 1],
                [No = 2, Value = 5.6789],         // 整数ではない
                [No = 3, Value = "Hello World!"], // テキスト型
                [No = 4, Value = true]            // 論理型
            },
            type table [No = Int64.Type, Value = Int64.Type] // 列は整数型
        )
in
    Source
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/c47e9cc1-dcad-340a-591e-d774046b38bf.png)
　Power Queryエディターを閉じてPower BI Desktopに戻ると、以下のようなエラーになってしまいます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/512cbe93-9890-f9ec-1e52-f2eb69b2b5bf.png)
　**整数型でない値は変換作業は行われず、全て空白(BLANK)となってしまいます。**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/e5fb7f9a-b707-3b79-d364-afde4723ce55.png)

　一方で、読み込む前に型の変換を行った場合は以下のようになります。

```
let
    Source = 
        Table.FromRecords(
            {
                [No = 1, Value = 1],
                [No = 2, Value = 5.6789],         // 整数ではない
                [No = 3, Value = "Hello World!"], // テキスト型
                [No = 4, Value = true]            // 論理型
            },
            type table [No = Int64.Type, Value = Int64.Type] // 列は整数型
        ),
    // 型の変換を行う
    ChangedType = 
        Table.TransformColumnTypes(
            Source,
            {
                {"Value", Int64.Type}
            }
        )
in
    ChangedType
```
　変換できなかった文字列はエラーとなりますが、10進数型は丸め処理が行われ、論理型はTrueが１、Falseが0になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/075b5ab4-7d60-0514-136f-684d02410dcd.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/1fdcbf34-485c-2935-8214-3512420f1a99.png)

### 銀行型丸め
　型の変換で10進型が整数に丸め処理されていますが、このとき行われる丸め処理の種類は **「銀行型丸め(RoundingMode.ToEven)」** です。
　銀行型丸めでは、最も近い整数に丸められ、中間の値は最も近い偶数に丸められます。つまり、整数に変換する場合、以下のようになります。

|元の数|丸め後||
|--:|--:|:--|
|1.4|1|
|1.5|2|偶数２に丸め|
|1.6|2|
|2.4|2|
|2.5|2|偶数2に丸め|
|2.6|3|

　このように、小数部が5の場合、整数部が偶数になる方に丸めが行われます。
　Power Queryの **Number.Round** では、この銀行丸めが標準となっていますので、通常の四捨五入を行う場合はオプションで **RoundingMode.AwayFromZero** を指定します。

```text:例
Round = Table.TransformColumns(
    Source,
    {
        {
            "Value", 
            each Number.Round(_, 0, RoundingMode.AwayFromZero)
        }
    }
)
```
https://learn.microsoft.com/ja-jp/powerquery-m/number-round

# 5. type number と Number.Type
　Table.FromRecordの型として設定するとき,type number という書き方と Number.Type という書き方の両方使うことができます。

```text:
let
    Source = 
        Table.FromRecords(
            {
                [
                    Col1 = 1.23,
                    Col2 = 1.23
                ]
            },
            type table [
                Col1 = (type number),  // type numberはカッコで括る必要があります
                Col2 = Number.Type
            ]
        )
in
    Source
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/3f0be0b9-acd9-4421-4823-8381f883ec18.png)
　このテーブルをTable.Schemaで表示すると、以下のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/864075/e74d37f0-6bf3-59ef-532b-5ac966e2c2a9.png)
　いずれも、TypeNameはNumber.Type、Kindはnumberとして設定されます。

# 6. 変換作業は必要か
　型の変換作業を行うと、列の値が型に合致するよう変換できます。また、変換できない値はエラーとなり、問題があることを警告してくれます。列の型と異なる値がテーブルに存在する場合、変換操作で修正するか、きちんと取り除いてやり、意図せぬデータの欠落は避けることが必要です。

　データ型の変換作業を行うタイミングは、「 **できるだけ早い時期に** 」行うのがよいでしょう。 

　値が完全に型に一致していることが保証されているのであれば、変換作業にコストを支払う必要はありませんが、そのコストは敢えて避ける必要があるほど大きいものとはなりません。

　型ファセットは主に外部（データソース、ホスト環境、ツール）と相互作用するときに関係することを忘れないでください。ファセットは言語層やマッシュアップエンジン層の動作に影響を与えません。ファセットを適用し、「.Type」で終わる名前を定義しても、新しいタイプやサブタイプは生成されるわけではありません。

# 関連記事
1. [[Power BI] Power Queryの『型』を探る (1) number](https://qiita.com/spumoni/items/9459635b69db773eca30)
1. [[Power BI] Power Queryの『型』を探る (2) text, temporal falimy](https://qiita.com/spumoni/items/ce38958d9858ea132a8f)
1. [[Power BI] Power Queryの『型』を探る (3) number 蛇足](https://qiita.com/spumoni/items/c46b3ddfb8173b6e9aa2)
1. [[Power BI] Power Queryの『型』を探る (4) null](https://qiita.com/spumoni/items/d8279858ceb627af8101)
1. [[Power BI] Power Queryの『型』を探る (5) カスタム型](https://qiita.com/spumoni/items/ec6893c9e4527b674b76)
1. [Power BIデータモデルの型システム (1) 数値型、日付／時刻型](https://qiita.com/spumoni/items/c73e465b691b15694ba0)
1. [Power BIデータモデルの型システム (2) テキスト型、論理型](https://qiita.com/spumoni/items/476494dc7818edc5e190)