こんな人にオススメ
astropy
で数値に単位を付与できたのはとても心強いけど、出力のフォーマット(書式)がなんだか気に食わない!他の書式で出力できないの?ということで、今回は
astropy
の単位の出力方法について見ていく。astropy
では単位の出力がm / s
だったりm s-1
だったりと複数種類に対応している。astropy
の単位付与については以下参照。
-
-
【astropy&単位】astropyで変数に単位を付与
続きを見る
python環境は以下。
- Python 3.9.2
- astropy 4.2
準備
まずは本記事で使用する値と単位を紹介する。その後、デフォルトでは単位がどのような出力になるのかを確認する。
本記事で使用する値と単位
本記事ではとにかく単位を長くするという意味で、特に意味のない値・単位を使用する。
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
個人的にはfits
かvounit
、だが...
個人的に普段使用するならfitsかvounitだが、毎回毎回単位を抽出して指定しないといけない。面倒。しかも単位の並び順については指定できない(と思っている)。今回の例では例えばW s2 m-3 Hz-1
の順番で出力したい。うーん、あんまり使わないかな。
関連記事
-
-
【astropy&単位】astropyで変数に単位を付与
続きを見る