カテゴリー

astropy

【astropy&単位】astropyの単位出力書式

2021年4月11日

こんな人にオススメ


astropyで数値に単位を付与できたのはとても心強いけど、出力のフォーマット(書式)がなんだか気に食わない!他の書式で出力できないの?

ということで、今回はastropyの単位の出力方法について見ていく。astropyでは単位の出力がm / sだったりm s-1だったりと複数種類に対応している。astropyの単位付与については以下参照。
【astropy&単位】astropyで変数に単位を付与

こんな人にオススメ 数値計 ...

続きを見る

python環境は以下。

  • Python 3.9.2
  • astropy 4.2

運営者のメガネとです。YouTubeTwitterInstagramも運営中。

自己紹介はこちらから、お問い合わせはこちらからお願いいたします。

運営者メガネ

準備

まずは本記事で使用する値と単位を紹介する。その後、デフォルトでは単位がどのような出力になるのかを確認する。

本記事で使用する値と単位

本記事ではとにかく単位を長くするという意味で、特に意味のない値・単位を使用する。

val = 1.234
unit = u.Unit('W nm-2')
quantity = val * unit

print(quantity)
# 1.234 W / nm2

とりあえず単位を伸ばした。本記事では最後のquantity4を使用する。

quantity2 = quantity * u.s ** 2
quantity3 = quantity2 / u.Hz
quantity4 = quantity3 / u.m ** 3

print(quantity2)
# 1.234 s2 W / nm2
print(quantity3)
# 1.234 s2 W / (Hz nm2)
print(quantity4)
# 1.234 s2 W / (Hz m3 nm2)

デフォルトの単位出力では、正符号を持つ単位が最初にきて、不負号の単位はスラッシュ/の後にカッコ()で一括管理されている。今回はこの部分を変更する方法について紹介する。

出力formatを変更

ここからは出力のフォーマットを変更し、どのように出力されるのかについて見ていく。

どんな引数があるのか

まずはどのような種類の引数があるのかについて調べる。「Units and Quantities (astropy.units)」の「astropy.units.format Package」では以下のような表示がされている(引用と表の同時表示ができないので、表をそのまま貼っている)。

(引用ここから)

Format description
Base The abstract base class of all unit formats.
Generic A “generic” format.
CDS Support the Centre de Données astronomiques de Strasbourg Standards for Astronomical Catalogues 2.0 format, and the complete set of supported units.
Console Output-only format for to display pretty formatting at the console.
Fits The FITS standard unit format.
Latex Output LaTeX to display the unit based on IAU style guidelines.
LatexInline Output LaTeX to display the unit based on IAU style guidelines with negative powers.
OGIP Support the units in Office of Guest Investigator Programs (OGIP) FITS files.
Unicode Output-only format to display pretty formatting at the console using Unicode characters.
Unscaled A format that doesn’t display the scale part of the unit, other than that, it is identical to the Generic format.
VOUnit The IVOA standard for units used by the VO.

(引用ここまで)

書式 説明
Base 全ての書式の基本書式
Generic 一般的な(generic)は書式
CDS 「Centre de Données astronomiques de Strasbourg Standards for Astronomical Catalogues 2.0 format」をサポートする書式。数値・単位間には半角スペースで、単位同士の間には.もしくは不負号なら/を入れる
Console 出力専用の書式で、分数を上下に配置したり累乗を^2というように表示する
Fits FITSの標準的な出力書式
Latex LaTeXの出力スタイルに沿った書式
LatexInline 負の符号を累乗の形式で表した場合でのLaTeX出力の書式
OGIP 「Office of Guest Investigator Programs (OGIP) FITS files」をサポートする書式で、累乗を**で表す
Unicode Unicode文字を使用して出力する出力専用書式
Unscaled Genericと同じ
VOUnit IVOAをサポートする書式で、不負号は単位の後にそのまま書かれる

上記のように表は存在するのだが、コードでこれら引数を出力したいので以下のコードを作成した。以下のコードではu.formatの属性を出力したのち、__を省いた値を全て小文字で出力している。なぜなら同じ出力内容でも小文字・大文字が存在するから。大文字の場合の出力フォーマットについては後述。

# formatに入っている要素を抽出し、全て小文字に
element_lst = []
dir_format = dir(u.format)
print(dir_format)
# ['Base', 'CDS', 'Console', 'Fits', 'Generic', 'Latex', 'LatexInline', 'OGIP', 'Unicode', 'Unscaled', 'VOUnit', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'base', 'cds', 'console', 'core', 'fits', 'generic', 'generic_lextab', 'generic_parsetab', 'get_format', 'latex', 'ogip', 'sys', 'unicode_format', 'utils', 'vounit']

# いらない要素を排除
for element in dir_format:
    # __付きの値はスルー
    if '_' not in element:
        element_lst.append(element.lower())
print(element_lst)
# ['base', 'cds', 'console', 'fits', 'generic', 'latex', 'latexinline', 'ogip', 'unicode', 'unscaled', 'vounit', 'base', 'cds', 'console', 'core', 'fits', 'generic', 'latex', 'ogip', 'sys', 'utils', 'vounit']

全文字列を小文字にしたので、かぶっている文字列が存在する。今度はこのかぶっている文字列を削除する。削除には集合で使われるsetを使用するのが一般的だと思うが、今回はコードを短くするという意味と自分でもあまり使ってこなかったdict.fromkeysを使用する。

dict.fromkeysは第一引数に入力した値に第二引数の値を入力するというもの。参考までに例を以下に示す。

# keysを配列で入れる
test1 = dict.fromkeys(['key1', 'key2'], 1)
print(test1)
# {'key1': 1, 'key2': 1}
test2 = dict.fromkeys(['key1', 'key2'], [1])
print(test2)
# {'key1': [1], 'key2': [1]}
test3 = dict.fromkeys(['key1', 'key2'], {'a': 10, 'b': 20})
print(test3)
# {'key1': {'a': 10, 'b': 20}, 'key2': {'a': 10, 'b': 20}}
test4 = dict.fromkeys(['key1', 'key2'], )
print(test4)
# {'key1': None, 'key2': None}

# 配列は要素が1つでも大丈夫
test5 = dict.fromkeys(['key1'], 1)
print(test5)
# {'key1': 1}

# keyを文字列で入れる
test6 = dict.fromkeys('key1', 1)
print(test6)
# {'k': 1, 'e': 1, 'y': 1, '1': 1}

# keyは数字はダメ
# test7 = dict.fromkeys(10, [1, 2])
# TypeError: 'int' object is not iterable

要素を一意にする。一応、同じ結果が得られるということでsetでの処理も書いておいた。setの場合は順序を保つためにはkeyを指定しないといけないので長くなるので、dict.fromkeysの方が短くてスッキリしている。

# 要素を一意にする
# element_lst = list(sorted(set(element_lst), key=element_lst.index))
unique_lst = list(dict.fromkeys(element_lst))
print(unique_lst)
# ['base', 'cds', 'console', 'fits', 'generic', 'latex', 'latexinline', 'ogip', 'unicode', 'unscaled', 'vounit', 'core', 'sys', 'utils']

実際に使用できる引数

前節で要素を一意にしたが、実はこの中でも使用できない要素がある。先程のastropyのサイトにないcoreなどがそう。ここではこれを排除する。シンプルに入力して出力可能なものだけ抽出する。

# formatとして使用できるものだけを抽出
format_lst = []
for unique in unique_lst:
    try:
        ok = u.format.get_format(unique)
        print(f"ok: {unique}")
        format_lst.append(unique)
    except ValueError:
        print(f"\\tng: {unique}")
# ok: base
# ok: cds
# ok: console
# ok: fits
# ok: generic
# ok: latex
# 	ng: latexinline
# ok: ogip
# ok: unicode
# ok: unscaled
# ok: vounit
# 	ng: core
# 	ng: sys
# 	ng: utils
print(format_lst)
# ['base', 'cds', 'console', 'fits', 'generic', 'latex', 'ogip', 'unicode', 'unscaled', 'vounit']

しかし、latexinlineがサイトには載っているのにここでは弾かれてしまった。上述の表のLatexinlineのリンクから該当ページに飛ぶと以下のような記述がある。

Attributes Documentation name = 'latex_inline'

すなわち、この直接の原因はわからないが、latex_inlineと書いてねということだろう。アンダースコア_が必要。

なので、以下のコードを追加して、latex_inlineを救済。

# latex_inlineを挿入
format_lst.insert(6, 'latex_inline')
print(format_lst)
# ['base', 'cds', 'console', 'fits', 'generic', 'latex', 'latex_inline', 'ogip', 'unicode', 'unscaled', 'vounit']

また、genericがサイトとは異なる位置に存在しているので、genericも位置を修正して救済。これでサイトに載っている表と同じ並びになった。

# genericの位置変更
format_lst.pop(4)
format_lst.insert(1, 'generic')
print(format_lst)
# ['base', 'generic', 'cds', 'console', 'fits', 'latex', 'latex_inline', 'ogip', 'unicode', 'unscaled', 'vounit']

実際に出力してみる

では実際にコードに反映させて出力してみる。しかし、baseでエラーが発生するのでtryで回避した。例外処理でエラーの内容を吐く時に使用。また、注意しなければいけないのは、単位のみの状態でしかこの書式設定ができないということ。単位の付与された値に対して.unitで単位飲みを出力し、その単位に対して書式設定を行う必要がある。値つきだとエラーになる。

tracebackについては「シラベルノート」の「Python で例外のエラーメッセージを取得するコード例」の「『最後の行』だけを取得」という項目を参考にさせていただいた。

# 抽出した文字列でformat
for name in format_lst:
    try:
        ans = f"{quantity4.unit:{name}}"
        print(f"{name}:\\n{ans}")
        print('\\n')
    except Exception as e:
        print(*traceback.format_exception_only(type(e), e))
# base:
# NotImplementedError: Can not output in Base format

# generic:
# s2 W / (Hz m3 nm2)

# cds:
# s2.W.Hz-1.nm-2.m-3

# console:
#     s^2 W
#  -----------
#  Hz nm^2 m^3

# fits:
# Hz-1 m-3 nm-2 s2 W

# latex:
# \$\\mathrm{\\frac{s^{2}\\,W}{Hz\\,nm^{2}\\,m^{3}}}\$

# latex_inline:
# \$\\mathrm{s^{2}\\,W\\,Hz^{-1}\\,nm^{-2}\\,m^{-3}}\$

# ogip:
# s**2 W / (Hz m**3 nm**2)

# unicode:
#    s² W
#  ─────────
#  Hz nm² m³

# unscaled:
# s2 W / (Hz m3 nm2)

# vounit:
# Hz-1 m-3 nm-2 s2 W

latex, latex_inlineを実際に$\LaTeX$表記で書いてみる、

$$\mathrm{\frac{s^{2}\,W}{Hz\,nm^{2}\,m^{3}}} \\\ \\ \mathrm{s^{2}\,W\,Hz^{-1}\,nm^{-2}\,m^{-3}}$$

ちゃんと合っている。

エラーを吐いたbaseについて

前節でbaseでエラーが出た。エラーはNotImplementedErrorというエラーでドキュメントの「組み込み例外」の「exception NotImplementedError」には以下のように書かれている。

exception NotImplementedError この例外は RuntimeError から派生しています。ユーザ定義の基底クラスにおいて、抽象メソッドが派生クラスでオーバライドされることを要求する場合にこの例外を送出しなくてはなりません。またはクラスは実装中であり本来の実装を追加する必要があることを示します。

要するに、オーバーライドとやらをしていないから発生している。このオーバーライドに関しては同じくドキュメントの「9. クラス」の「9.5. 継承」に以下のように書いている。

派生クラスは基底クラスのメソッドを上書き (override) することができます。

要するに、baseは基礎的なもので、上書きされていないから使えないよということなのだろう。確かに、astropyのサイトにも

The abstract base class of all unit formats.

とあるのでそうなのだろう。

大文字はどうなの?

ということで前章で出力を見てきたわけだが、全て一旦小文字に変換して行った。最後に大文字入りの場合を検証して終わる。検証方法は上記と同様。

# 大文字はどうなのか検証

# 要素の取得
for element in dir_format:
    # __付きの値はスルー
    if '_' not in element:
        element_lst.append(element)
print(element_lst)
# ['base', 'cds', 'console', 'fits', 'generic', 'latex', 'latexinline', 'ogip', 'unicode', 'unscaled', 'vounit', 'base', 'cds', 'console', 'core', 'fits', 'generic', 'latex', 'ogip', 'sys', 'utils', 'vounit', 'Base', 'CDS', 'Console', 'Fits', 'Generic', 'Latex', 'LatexInline', 'OGIP', 'Unicode', 'Unscaled', 'VOUnit', 'base', 'cds', 'console', 'core', 'fits', 'generic', 'latex', 'ogip', 'sys', 'utils', 'vounit']

# 要素を一意にする
# element_lst = list(sorted(set(element_lst), key=element_lst.index))
unique_lst = list(dict.fromkeys(element_lst)) + ['latexinline', 'LatexInline']
print(unique_lst)
# ['base', 'cds', 'console', 'fits', 'generic', 'latex', 'latexinline', 'ogip', 'unicode', 'unscaled', 'vounit', 'core', 'sys', 'utils', 'Base', 'CDS', 'Console', 'Fits', 'Generic', 'Latex', 'LatexInline', 'OGIP', 'Unicode', 'Unscaled', 'VOUnit', 'latexinline', 'LatexInline']

# 小文字、大文字の順番に変更
unique_lst = sorted(unique_lst, key=str.lower)
print(unique_lst)
# ['base', 'Base', 'cds', 'CDS', 'console', 'Console', 'core', 'fits', 'Fits', 'generic', 'Generic', 'latex', 'Latex', 'latexinline', 'LatexInline', 'latexinline', 'LatexInline', 'ogip', 'OGIP', 'sys', 'unicode', 'Unicode', 'unscaled', 'Unscaled', 'utils', 'vounit', 'VOUnit']

# formatとして使用できるものだけを抽出
format_lst = []
for unique in unique_lst:
    try:
        ok = u.format.get_format(unique)
        print(f"ok: {unique}")
        format_lst.append(unique)
    except ValueError:
        print(f"\\tng: {unique}")
# ok: base
# ok: Base
# ok: cds
# ok: CDS
# ok: console
# ok: Console
# 	ng: core
# ok: fits
# ok: Fits
# ok: generic
# ok: Generic
# ok: latex
# ok: Latex
# 	ng: latexinline
# 	ng: LatexInline
# 	ng: latexinline
# 	ng: LatexInline
# ok: ogip
# ok: OGIP
# 	ng: sys
# ok: unicode
# ok: Unicode
# ok: unscaled
# ok: Unscaled
# 	ng: utils
# ok: vounit
# ok: VOUnit

print(format_lst)
# ['base', 'Base', 'cds', 'CDS', 'console', 'Console', 'fits', 'Fits', 'generic', 'Generic', 'latex', 'Latex', 'ogip', 'OGIP', 'unicode', 'Unicode', 'unscaled', 'Unscaled', 'vounit', 'VOUnit']

# 抽出した文字列でformat
for name in format_lst:
    try:
        ans = f"{quantity4.unit:{name}}"
        print(f"{name}:\\n{ans}")
        print('\\n')
    except Exception as e:
        print(f"{name}:")
        print(*traceback.format_exception_only(type(e), e))
# base:
# NotImplementedError: Can not output in Base format

# Base:
# NotImplementedError: Can not output in Base format

# cds:
# s2.W.Hz-1.nm-2.m-3

# CDS:
# s2.W.Hz-1.nm-2.m-3

# console:
#     s^2 W
#  -----------
#  Hz nm^2 m^3

# Console:
#     s^2 W
#  -----------
#  Hz nm^2 m^3

# fits:
# Hz-1 m-3 nm-2 s2 W

# Fits:
# Hz-1 m-3 nm-2 s2 W

# generic:
# s2 W / (Hz m3 nm2)

# Generic:
# s2 W / (Hz m3 nm2)

# latex:
# \$\\mathrm{\\frac{s^{2}\\,W}{Hz\\,nm^{2}\\,m^{3}}}\$

# Latex:
# \$\\mathrm{\\frac{s^{2}\\,W}{Hz\\,nm^{2}\\,m^{3}}}\$

# ogip:
# s**2 W / (Hz m**3 nm**2)

# OGIP:
# s**2 W / (Hz m**3 nm**2)

# unicode:
#    s² W
#  ─────────
#  Hz nm² m³

# Unicode:
#    s² W
#  ─────────
#  Hz nm² m³

# unscaled:
# s2 W / (Hz m3 nm2)

# Unscaled:
# s2 W / (Hz m3 nm2)

# vounit:
# Hz-1 m-3 nm-2 s2 W

# VOUnit:
# Hz-1 m-3 nm-2 s2 W

個人的にはfitsvounit、だが...

個人的に普段使用するならfitsかvounitだが、毎回毎回単位を抽出して指定しないといけない。面倒。しかも単位の並び順については指定できない(と思っている)。今回の例では例えばW s2 m-3 Hz-1の順番で出力したい。うーん、あんまり使わないかな。

関連記事

【astropy&単位】astropyで変数に単位を付与

こんな人にオススメ 数値計 ...

続きを見る

スイッチボット

2022/11/28

【SwitchBotロックレビュー】これからのスタンダードになりうるスマートロック

こんな人にオススメ SwitchBotからスマートロック「SwitchBotロック」が発売された ...

生活に役立つ

2022/11/28

【メガネ厳選】クソ便利に使っているサービスやアイテム達

このページでは執筆者「メガネ」が実際に使って便利だと感じているサ ...

マウス

2022/9/11

【Logicool MX ERGO vs MX Master 3】ERGOをメインにした決定的な理由

こんな疑問・お悩みを持っている人におすすめ 執筆者はLogicoolのハイエンӠ ...

完全ワイヤレスイヤホン(TWS)

2022/11/21

【ながら聴きイヤホン比較】SONY LinkBuds、ambie、BoCoはどれがおすすめ?

こんな人におすすめ 耳を塞がない開放型のイヤホンに完全ワイヤレスӟ ...

macOSアプリケーション

2022/10/15

【M1 Mac】MacBook Proに入れている便利でニッチなアプリを21個紹介する

こんな人におすすめ MacBookを購入してLINEとか必要最低限のアプリは入れた。 ...

完全ワイヤレスイヤホン(TWS)

2022/10/23

【SENNHEISER MOMENTUM True Wireless 3レビュー】高レベルでバランス型の高音質イヤホン

こんな人におすすめ SENNHEISER MOMENTUM True Wireless 3って実際のところどうなの? 評判は良い ...

完全ワイヤレスイヤホン(TWS)

2022/11/21

【SONY WF-1000XM4レビュー】神とゴミのハーフ&ハーフ

こんな人におすすめ SONYのフラグシップモデル「SONY WF-1000XM4」ってどれくらい性 ...

完全ワイヤレスイヤホン(TWS)

2022/8/19

【Nothing ear (1)レビュー】ライトな完成度、アップデートに期待

こんな人にオススメ 完全ワイヤレスイヤホン(TWS)でスケルトンボディ ...

Pythonを学びたいけど独学できる時間なんてない人へのすゝめ

執筆者は大学の研究室・大学院にて独学でPythonを習得した。

でも社会人になったら独学で行うには時間も体力もなくて大変だ。

時間がない社会人だからこそプロの教えを乞うのが効率的。

ここでは色んなタイプに合ったプログラミングスクールの紹介をする。

  • この記事を書いた人

メガネ

ベンチャー企業のWebエンジニア駆け出し。独学のPythonで天文学系の大学院を修了→新卒を1.5年で辞める→転職→今に至る。
常時金欠のガジェット好きでM1 MacBook Pro x Galaxy S22 Ultraの狂人。
人見知りで根暗だったけど、人生楽しもうと思って良い方向に狂う→人生が楽しい

ガジェットのレビューとPythonコードを記事にしています。ぜひ楽しんでください🦊
自己紹介と半生→変わって楽しいの繰り返し

-astropy
-,