[Python][Seaborn] Swarmplotが便利

Swarmplotの利点

Seabornライブラリのswarmplotがとても便利、というだけの記事。

import matplotlib.pyplot as plt
import seaborn as sns

df = sns.load_dataset('iris')
fig, ax = plt.subplots()
sns.swarmplot(x='species', y='sepal_length', data=df, ax=ax)
fig.savefig('sepal_length.jpg', dpi=300)
plt.close(fig)

f:id:cyanatlas:20201010174339j:plain

swarmplotを使うと、同じ値が複数あるときに横にずらして並べてくれるので、全体的な傾向が分かりやすい。

この図の場合、sepal_length(花のがくの長さ)の平均は、setosa種<versicolor種<virginica種、という傾向にありそうだが、同時に分散も大きくなっていそうだというのが見てとれる。

Swarmplotの限界

ただしswarmplotにも限界はあって、同じ値を持つデータ数が多くなりすぎると、横にずらすスペースがなくなって、最終的に重なってしまう。

下の図は、先程のデータセットを使ってpetal_length(花弁の長さ)をプロットしたもの。

f:id:cyanatlas:20201010175148j:plain

左のsetosa種では似たような値が多いために、横の方で複数の点が重なってしまっている。そうなると、頻度が分かりにくくなり、swarmplotを使う利点は小さくなる。

もしswarmplotで綺麗に描けないくらい値の似たデータが多いなら、カテゴリごとにヒストグラムを描いて並べるのが良いだろう。ただその場合は、ヒストグラムの切り方(bins)に注意を払う必要がある。

[Python][Pandas] いずれかの文字列と合致するorを含む行を抽出する

はじめに

文字列のリストが用意されていて、PandasのDataFrameからいずれかの文字列と合致するor含む行を取り出したいときがある。

Forループを使えば書けるのだが、Numpyを使えばもっとシンプルに書けることに気づいたのでまとめておく。

テスト用のDataFrameを読み込む

seabornライブラリからirisデータセットを読み込む。

import seaborn as sns

df = sns.load_dataset('iris')
df.to_csv('iris.csv')

irisデータセットは、文字列の種名を含んでいる。

sepal_length sepal_width petal_length petal_width species
0 5.1 3.5 1.4 0.2 setosa
1 4.9 3.0 1.4 0.2 setosa
2 4.7 3.2 1.3 0.2 setosa
3 4.6 3.1 1.5 0.2 setosa
4 5.0 3.6 1.4 0.2 setosa
5 5.4 3.9 1.7 0.4 setosa
6 4.6 3.4 1.4 0.3 setosa
7 5.0 3.4 1.5 0.2 setosa
8 4.4 2.9 1.4 0.2 setosa
9 4.9 3.1 1.5 0.1 setosa
10 5.4 3.7 1.5 0.2 setosa
11 4.8 3.4 1.6 0.2 setosa
12 4.8 3.0 1.4 0.1 setosa
13 4.3 3.0 1.1 0.1 setosa
14 5.8 4.0 1.2 0.2 setosa
15 5.7 4.4 1.5 0.4 setosa
16 5.4 3.9 1.3 0.4 setosa
17 5.1 3.5 1.4 0.3 setosa
18 5.7 3.8 1.7 0.3 setosa
19 5.1 3.8 1.5 0.3 setosa
20 5.4 3.4 1.7 0.2 setosa
21 5.1 3.7 1.5 0.4 setosa
22 4.6 3.6 1.0 0.2 setosa
23 5.1 3.3 1.7 0.5 setosa
24 4.8 3.4 1.9 0.2 setosa
25 5.0 3.0 1.6 0.2 setosa
26 5.0 3.4 1.6 0.4 setosa
27 5.2 3.5 1.5 0.2 setosa
28 5.2 3.4 1.4 0.2 setosa
29 4.7 3.2 1.6 0.2 setosa
30 4.8 3.1 1.6 0.2 setosa
31 5.4 3.4 1.5 0.4 setosa
32 5.2 4.1 1.5 0.1 setosa
33 5.5 4.2 1.4 0.2 setosa
34 4.9 3.1 1.5 0.2 setosa
35 5.0 3.2 1.2 0.2 setosa
36 5.5 3.5 1.3 0.2 setosa
37 4.9 3.6 1.4 0.1 setosa
38 4.4 3.0 1.3 0.2 setosa
39 5.1 3.4 1.5 0.2 setosa
40 5.0 3.5 1.3 0.3 setosa
41 4.5 2.3 1.3 0.3 setosa
42 4.4 3.2 1.3 0.2 setosa
43 5.0 3.5 1.6 0.6 setosa
44 5.1 3.8 1.9 0.4 setosa
45 4.8 3.0 1.4 0.3 setosa
46 5.1 3.8 1.6 0.2 setosa
47 4.6 3.2 1.4 0.2 setosa
48 5.3 3.7 1.5 0.2 setosa
49 5.0 3.3 1.4 0.2 setosa
50 7.0 3.2 4.7 1.4 versicolor
51 6.4 3.2 4.5 1.5 versicolor
52 6.9 3.1 4.9 1.5 versicolor
53 5.5 2.3 4.0 1.3 versicolor
54 6.5 2.8 4.6 1.5 versicolor
55 5.7 2.8 4.5 1.3 versicolor
56 6.3 3.3 4.7 1.6 versicolor
57 4.9 2.4 3.3 1.0 versicolor
58 6.6 2.9 4.6 1.3 versicolor
59 5.2 2.7 3.9 1.4 versicolor
60 5.0 2.0 3.5 1.0 versicolor
61 5.9 3.0 4.2 1.5 versicolor
62 6.0 2.2 4.0 1.0 versicolor
63 6.1 2.9 4.7 1.4 versicolor
64 5.6 2.9 3.6 1.3 versicolor
65 6.7 3.1 4.4 1.4 versicolor
66 5.6 3.0 4.5 1.5 versicolor
67 5.8 2.7 4.1 1.0 versicolor
68 6.2 2.2 4.5 1.5 versicolor
69 5.6 2.5 3.9 1.1 versicolor
70 5.9 3.2 4.8 1.8 versicolor
71 6.1 2.8 4.0 1.3 versicolor
72 6.3 2.5 4.9 1.5 versicolor
73 6.1 2.8 4.7 1.2 versicolor
74 6.4 2.9 4.3 1.3 versicolor
75 6.6 3.0 4.4 1.4 versicolor
76 6.8 2.8 4.8 1.4 versicolor
77 6.7 3.0 5.0 1.7 versicolor
78 6.0 2.9 4.5 1.5 versicolor
79 5.7 2.6 3.5 1.0 versicolor
80 5.5 2.4 3.8 1.1 versicolor
81 5.5 2.4 3.7 1.0 versicolor
82 5.8 2.7 3.9 1.2 versicolor
83 6.0 2.7 5.1 1.6 versicolor
84 5.4 3.0 4.5 1.5 versicolor
85 6.0 3.4 4.5 1.6 versicolor
86 6.7 3.1 4.7 1.5 versicolor
87 6.3 2.3 4.4 1.3 versicolor
88 5.6 3.0 4.1 1.3 versicolor
89 5.5 2.5 4.0 1.3 versicolor
90 5.5 2.6 4.4 1.2 versicolor
91 6.1 3.0 4.6 1.4 versicolor
92 5.8 2.6 4.0 1.2 versicolor
93 5.0 2.3 3.3 1.0 versicolor
94 5.6 2.7 4.2 1.3 versicolor
95 5.7 3.0 4.2 1.2 versicolor
96 5.7 2.9 4.2 1.3 versicolor
97 6.2 2.9 4.3 1.3 versicolor
98 5.1 2.5 3.0 1.1 versicolor
99 5.7 2.8 4.1 1.3 versicolor
100 6.3 3.3 6.0 2.5 virginica
101 5.8 2.7 5.1 1.9 virginica
102 7.1 3.0 5.9 2.1 virginica
103 6.3 2.9 5.6 1.8 virginica
104 6.5 3.0 5.8 2.2 virginica
105 7.6 3.0 6.6 2.1 virginica
106 4.9 2.5 4.5 1.7 virginica
107 7.3 2.9 6.3 1.8 virginica
108 6.7 2.5 5.8 1.8 virginica
109 7.2 3.6 6.1 2.5 virginica
110 6.5 3.2 5.1 2.0 virginica
111 6.4 2.7 5.3 1.9 virginica
112 6.8 3.0 5.5 2.1 virginica
113 5.7 2.5 5.0 2.0 virginica
114 5.8 2.8 5.1 2.4 virginica
115 6.4 3.2 5.3 2.3 virginica
116 6.5 3.0 5.5 1.8 virginica
117 7.7 3.8 6.7 2.2 virginica
118 7.7 2.6 6.9 2.3 virginica
119 6.0 2.2 5.0 1.5 virginica
120 6.9 3.2 5.7 2.3 virginica
121 5.6 2.8 4.9 2.0 virginica
122 7.7 2.8 6.7 2.0 virginica
123 6.3 2.7 4.9 1.8 virginica
124 6.7 3.3 5.7 2.1 virginica
125 7.2 3.2 6.0 1.8 virginica
126 6.2 2.8 4.8 1.8 virginica
127 6.1 3.0 4.9 1.8 virginica
128 6.4 2.8 5.6 2.1 virginica
129 7.2 3.0 5.8 1.6 virginica
130 7.4 2.8 6.1 1.9 virginica
131 7.9 3.8 6.4 2.0 virginica
132 6.4 2.8 5.6 2.2 virginica
133 6.3 2.8 5.1 1.5 virginica
134 6.1 2.6 5.6 1.4 virginica
135 7.7 3.0 6.1 2.3 virginica
136 6.3 3.4 5.6 2.4 virginica
137 6.4 3.1 5.5 1.8 virginica
138 6.0 3.0 4.8 1.8 virginica
139 6.9 3.1 5.4 2.1 virginica
140 6.7 3.1 5.6 2.4 virginica
141 6.9 3.1 5.1 2.3 virginica
142 5.8 2.7 5.1 1.9 virginica
143 6.8 3.2 5.9 2.3 virginica
144 6.7 3.3 5.7 2.5 virginica
145 6.7 3.0 5.2 2.3 virginica
146 6.3 2.5 5.0 1.9 virginica
147 6.5 3.0 5.2 2.0 virginica
148 6.2 3.4 5.4 2.3 virginica
149 5.9 3.0 5.1 1.8 virginica

DataFrameからある一つの文字列と合致する行を取り出す

DataFrameからある一つの文字列と合致する行を取り出すには、次のように書けば良い。

import seaborn as sns

df = sns.load_dataset('iris')
df_setosa = df[df['species'] == 'setosa'].copy()
df_setosa.to_csv('setosa.csv')

これで種名が'setosa'の行だけを取り出すことができる。

DataFrameから複数の文字列のいずれかと合致する行を取り出す

irisデータセットから、種名が'setosa'あるいは'virginica'である行だけを取り出したい。
この場合、Numpyのanyメソッドを使うとシンプルに書くことができる。

import numpy as np
import seaborn as sns

df = sns.load_dataset('iris')
species_list = ['setosa', 'virginica']
df_se_vi = df[
    np.array([df['species'] == aspecies for aspecies in species_list]).any(axis=0)
].copy()
df_se_vi.to_csv('setosa_virginica.csv')

この書き方の場合、species_listの要素数が増減しても変更の必要がないというメリットがある。

DetaFrameから複数の文字列のいずれかを含む行を取り出す

条件式の==str.containsメソッドに変えれば良い。

import numpy as np
import seaborn as sns

df = sns.load_dataset('iris')
part_list = ['set', 'vir']
df_set_vir = df[
    np.array([df['species'].str.contains(part) for part in part_list]).any(axis=0)
]
df_set_vir.to_csv('set_vir.csv')

Wikipedia「Narrative structure」の和訳

少し興味を持ったので、訳しつつ読んでみた。感想としては、分類のセクションはなるほどと思ったが、他の箇所は話がつぎはぎで得るものは少なかったかなという印象。

原文はこちら(2020年6月27日版)。
en.wikipedia.org

注意

  • ざっくり和訳なので厳密な訳ではありません。
  • とくに専門用語に関しては不自然な和訳をしている可能性があります。

====================

物語構造 Narrative Structure

物語構造は文学の要素であり、一般には、物語が読者・聞き手・視聴者に提示される際の順序と方法の基礎をなす構造的枠組みとして記述される。物語のテキストの構造は、プロットと背景である。

目次

1. 定義 Definition

物語構造はストーリーとプロット、すなわちストーリーの内容とストーリーを伝えるために使われる形式に関するものである。ストーリーは、時系列順に記述された劇中の出来事を表す。プロットは、ストーリーがどのように伝えられるかを表す。ストーリーは重大な対立や主要な登場人物、背景や出来事を決定するもののことである。プロットはどのように、どの段階で、重大な対立が生じ、そして解消されるかについてである。

2. 説明 Description

第一幕では、すべての主要な登場人物と彼らの基本的な状況が紹介され、最も基本的な人物描写が含まれる。ある問題が導入され、これが物語を推し進めていく。

第二幕は物語の主要部分であり、ある出来事が物事を動かしていく。出来事の結果として、登場人物は人生における大きな変化を経験する。これはキャラクターアーク(訳者注:登場人物の変化のこと?)と呼ばれる。

第三幕では、ストーリーにおける問題が進展し、登場人物はその問題に直面せざるをえなくなる。ストーリーの全ての要素がうまくいき、必然的にエンディングへと向かう。

3. 歴史 History

物語構造の概念は最初に古代ギリシャの哲学者によって記述された。20世紀中期~後期に、ロラン・バルトウラジーミル・プロップ、ジョーゼフ・キャンベル、ノースロップ・フライといった構造主義文学研究者らが、すべての人間の物語がある普遍的で深い構造を持っているという議論を試み、その中で物語構造は重要な概念として見直された。ミシェル・フーコージャック・デリダなどのポスト構造主義の支持者は、そのように普遍的に共有された深い構造は論理的に不可能であると主張し、この論争の流行は終わった。

『批評の解剖』(Anatomy of Criticism)でノースロップ・フライは彼が春、夏、秋、冬の神話と呼んだものについて広範に取り扱っている。

※訳者補足:春の神話(悪⇒良)、夏の神話(良⇒良)、秋の神話(良⇒悪)、冬の神話(悪⇒悪)、ということだろうか?

4. 分類 Categories

物語の多くの形式は、四つの主要なカテゴリのどれかに入る。

  • 線形な物語は、最も普遍的な叙述である。ここでは出来事はおおよそ時系列順に表現される。すなわち、出来事は起こった順に説明される。
  • 非線形な物語(分裂した物語、分断された物語)では、出来事が時系列とは異なる順序で説明されたり、直接的な因果関係を追わないような方法で説明されたりする。
  • インタラクティブな叙述は、読者によって動き出す線形な物語作品のことを指す。
  • インタラクティブな物語は、読者が物語に影響を与えるような選択が可能なフィクションの形式のことである。例えば、読者の選択によって、別のプロットや別の結末にたどり着いたりする。

4.1 線形な物語 Linear narrative

回想は線形でない真の物語とよく混同されるが、回想の構造は基本的に線形である。オーソン・ウェルズの『市民ケーン』が一例である。いくつかの映画は結末から始まるが、回想映画はほとんど即座にストーリーの最初に戻り、そこから線形にストーリーが進んでいき、通常は映画の冒頭で見た結末と思われる過去まで進行する。

4.2 非線形な物語 Nonlinear narrative

映画は壊れた物語を通して単なる幻想(訳者注:錯覚?幻影?)を提示することもでき、その有名な例が1994年のパルプフィクションである。この映画は表向きには三つの短いストーリーからなり、これらは時系列的が崩壊した一つのストーリーの三つのセクションをなす。クエンティン・タランティーノは、古典的なフラッシュバックのテクニックを使うことなく、物語を構築したのである。

非線形な物語をもとに映画を制作するというさらなる野心的な試みに、1993年のAlain Resnaisによるフランス映画『Smoking/No Smoking』がある。この映画のプロットには、並行した展開が含まれており、登場人物が異なる選択をした場合のアイデアのもとで進行する。

映画の他には、いくつかの小説も非線形な方法で物語を表現している。Creative writingの教授Jane Alisonは2019年の本『Meander, Spiral, Explode: Design and Pattern in Narrative』のなかで、非線形な物語のパターンとして、螺旋、波、湾曲といったものを説明している。Chitra Banerjee Divakaruniの小説『Before We Visit the Goddess』の章は、出来事は線形な順序ではなく、むしろ特定の文学的手法を実現するような順序で整理されている。これにより、ストーリーをより面白くなるだけでなく、小説の中の登場人物は信用できる一生のタイムラインを持つことになる。

4.3 インタラクティブな叙述 Interactive narration

インタラクティブな叙述の作品では、たった一つの物語が存在するが、ユーザーは物語の次のピースを手に入れるため、あるいは筋が通った物語を組み立てるため、ユーザーは能動的に動かなければならない。

これは現代のビデオゲームに見られるような物語の手法である。プレイヤーは物語を進行させるため、ある目標を達成したり、ある作業を完遂したり、あるパズルを解いたり、あるレベルを終えたりしなければならない。

4.4 インタラクティブな物語 Interactive narrative

インタラクティブな物語は、枝分かれ構造をからなり、始まりの状況から複数の進展や結末に繋がる。そのような全てのゲームの原則は、物語のいずれの段階においても、ユーザーが選択することでストーリーが進展し、それによってまた新たな一連の選択肢が生まれるということである。このように、非線形な物語や会話を書くには、並行する多数の定まらないストーリーの存在を想像する必要がある。

ゲームブックでは、読者はストーリーを続けるためにした選択に基づき、特定のページを開くように指示される。典型的には、選択肢は会話ではなく行動である。例えば、主人公は別の部屋から物音を聞き、ドアを開けて調べるか、または逃げるか、あるいは助けを呼ぶかキメなければならない。この種のインタラクティブな体験は、ビデオゲームや本で可能だが、他の形式の娯楽にはあまり適さない。即興劇は同様に自由だが、もちろん「即興劇を書く」とは言わない。

5 図的な物語

漫画などに見られるシンプルな図的物語には、四つの段階がある。

  1. 登場人物の紹介と状況の説明
  2. 問題の導入、予期せぬチャンス、または状況の複雑化
  3. 一人または複数人の登場人物が問題に対して部分的または完全に対応する形での問題の解消
  4. 問題への対応によって明白な成功、部分的成功、不成功、不確かな成功が生じる(大団円、結末)

この四つの段階は、物語の複雑化と解決の段階にとって代わる物によって元々の状況がどのように変化したかを示しているのかもしれない(訳者注:この一文は意味がとれなかった)。

単純な物語では、この四つの段階は順に現れる。つまり、出来事は時系列順に説明される。より複雑なストーリーでは、出来事が説明される順序は変化する。実際に、そのようなストーリーは、大団円やその後の現在の状況、複雑化、回想における解決から始まることがある。しかし、これはシンプルな物語の場合とは異なる。

6. 関連項目

7. 参考文献・資料

(省略)

[Python][Pandas] Pandasの基本用法まとめ

環境:Windows10 + Anaconda 4.8.1 + Python 3.7 + Pandas 1.0.5

目次

はじめに

PythonにはPandasというデータ分析用のライブラリがある。使いこなせれば、CSVファイルなど表形式のデータからいろいろな情報を引き出すことができる。

一方で、Pythonの基本文法とは異なる、ライブラリ特有の文法に関する知識が必要なため、最初のハードルは決して低くないように思う。Pandasの文法は、Pythonの基本文法と違うだけでなく、Numpyとも違い、Rとも違うと感じる。ゆえに直観的に理解しづらい。

そこでこのページでは、Pandasの基本的な用法をまとめることにする。自分の勉強も兼ねている部分があるので、足りない部分やまだ分からない部分もあるが、随時書き足していけたらと思う。

準備

インストール

Anaconda環境の場合は

conda install pandas

としてインストールする。

バージョン

大きく二つのバージョンが存在する。

  • ver0.25系(記事執筆時点では0.25.3が最新)
  • ver1.0系(記事執筆時点では1.0.5が最新)

pandas.pydata.org
pandas.pydata.org

バージョン1.0.0がリリースされたのは2020年1月29日と比較的最近のことであり(記事執筆時点においては)、ver0.25系を用いているプログラムもまだまだ多いと思われる。ver1.0では一部の機能が削除されており、公式ドキュメントではver0.25からver1.0に更新する前にソースコードが警告なしで作動するか確認するようにとアナウンスしている*1

新しくインストールする場合は、ver1.0をインストールすればよい。すでにインストール済みの場合は、更新する前に確認することをおすすめする(仮想環境を新たに作成するのも一つの手)。

インポート

import pandas as pd

とするのが一般的。

サンプルデータ

Pandasの機能を試すために、サンプルデータを使いたいときがある。しかし調べた限りでは、Pandasにはサンプルデータは含まれていなかった。そこで代わりにseabornライブラリから読み込むことにする。

seaborn.load_dataset('iris')  # --> pandas.DataFrame

ここではseabornライブラリからiris(アヤメ)のデータを読み込んでいる。seabornライブラリから読み込んでも、pandasのDataFrameで返してくれる。

Pandasの型

Pandasには多数の型があり、把握するだけでも一苦労である。Pandasの型については次のページが詳しい。

https://pbpython.com/pandas_dtypes.html
note.nkmk.me

ここでは使用頻度の高そうな型にしぼって取り上げる。

  • DataFrame:データフレーム。データセットは主にこの型で処理される。
  • Series:シーケンス。Seriesが集まったものがDataFrameである。
  • 要素:シーケンスの要素にはいろいろな型がある。主なものを以下に列挙する。
    • int64:整数
    • float64:倍精度浮動小数
    • bool:ブール値
    • object:文字列または型の混合
    • datetime64:日付を表す
    • timedelta:時間差を表す
    • category:カテゴリカルなデータを表す

いま扱っている変数はDataFrameかSeriesか要素か、要素であれば何の型か、を意識しながらプログラムを書くことになる。

データフレームの作成

いくつかの方法があるが、最も簡単な方法を一つ示す。

pandas.DataFrame(
    data, columns=['column0', 'column1', 'column2'], 
    dtype={'column0': 'float', 'column1': 'int', 'column2': 'object'}
)

作成したリスト内リスト(行列)を、pandasのDataFrameに変換する。列名をcolumns引数で指定し、各列の型をdtype引数に辞書で指定する(オプション)。

CSVファイルの読み込み・書き出し

Pandasでデータを処理するにあたって、CSVファイルは最も基本となるファイルの一つ。DataFrameをCSVで保存したり、CSVからDataFrameを読み込んだりする。

pandas.read_csv(filename, sep=',') -> pandas.DataFrame

CSVファイルを読み込んでDataFrameを返す。あらかじめopen関数を使ってファイルを開いておく必要はない。タブ区切りの場合はsep='/t'と指定する.

pandas.DataFrame.to_csv(filename) -> Union[str, NoneType]

DataFrameをCSVファイルに書き込む。こちらもあらかじめファイルを開いておく必要はない。デフォルトではカンマ区切り。ちなみに、Excelはタブ区切りのCSVファイルを一発で読み込めないため、Excelを使う予定ならタブ区切りよりカンマ区切りのCSVファイルの方が便利。ファイル名を指定しない場合は、ファイルに書き込まれるはずだった文字列が戻り値となる。

Seriesの基本操作

DataFrameから一列取り出すとSeriesになる。まずはSeriesから要素を取り出す方法を示す。

pandas.Series.at[label]など

pandas.Series.at[label]  # -> 要素
pandas.Series.iat[n]  # -> 要素
pandas.Series.loc[label1:label2]  # -> pandas.Series
pandas.Series.iloc[n1:n2]  # -> pandas.Series

以上の四つの方法では、「取り出すもの」と「要素の指定方法」に違いがある。

取り出すもの 要素の指定方法
at 単独の要素 ラベル(インデックス)
iat 単独の要素 整数
loc 複数の要素 ラベル(インデックス)
iloc 複数の要素 整数

ラベルを使って指定するか整数を使って指定するかという違いはあるが、おそらく多くのSeriesではラベルが整数であるため(つまりlabelが整数)、どちらを使っても同じにはなる。ただし、ilocでは最後の値(n2)を含まないのに対し、locでは最後の値(label2)を含むことに注意する必要がある。

とは言え、上記四つの方法を目的に応じて使い分けるのはやや面倒なので、巨大なデータでなければnumpy.ndarrayやlistに変換してから要素にアクセスするの方が、間違いがなくて良いと思う。

numpy.ndarrayやlistに変換する方法を示す。

pandas.Series.values -> numpy.ndarray

Seriesをnumpy.ndarrayに変換して返す。

pandas.Series.to_list() -> [Any, ...]

Seriesを標準Pythonのリストに変換して返す。valuesインスタンス変数だが、to_listはメソッドであることに注意。

pandas.Series.index.values -> numpy.ndarray

Seriesのラベル(インデックス)を返す。たいていは[0, 1, 2, ...]という整数のシーケンス。最後にvaluesを着けない場合は、イテラブルなpandas.RangeIndexというオブジェクトが返される。

Seriesの長さや要素の型は、インスタンス変数に入っている。

pandas.Series.size -> int

Seriesが持つ要素の数を返す。配列の長さと同じ。

pandas.Series.dtype -> numpy.dtype

要素の型を返す.

DataFrameの基本操作

pandas.DataFrame.at[label_row, label_col]など

pandas.DataFrame.at[label_row, label_col]  # -> 要素
pandas.DataFrame.iat[n_row, n_col]  # -> 要素
pandas.DataFrame.loc[label1_row:label2_row, label1_col:label2_col]  # -> pandas.DataFrame
pandas.DataFrame.iloc[n1_row:n2_row, n1_col:n2_col]  # -> pandas.DataFrame

Seriesの場合と同様に、atとiatは単独の要素を取得するのに対し、locとilocは複数の要素(この場合はDataFrame)を取得する。atとlocはラベル(インデックス)を用いて、iatとilocは整数を用いて位置を指定する。

pandas.DataFrame.head(n=5) -> pandas.DataFrame

データフレームの先頭n行を返す(デフォルトでは5行)。printして中身を確認するといった用途に。

pandas.DataFrame.columns.values -> numpy.ndarray

列名をリストで取得する。

pandas.DataFrame.index.values -> numpy.ndarray

行名をリストで取得する。

pandas.DataFrame['column'] -> pandas.Series

列名columnのSeriesを取得する。

イテレーション

pandas.DataFrame.iterrows() -> Iterable

Indexとrowのtupleを返すイテラブル。rowはpandas.Seriesに変換されている。データにはrow[‘column’]という形でアクセス可能。ただしこのメソッドは遅いので、可能ならば次のメソッドを使う方が良い。

pandas.DataFrame.itertuples() -> Iterable

一行ずつ返すイテラブル。行はPandasという名前のnamedtupleに変換されている。データにはrow[0]またはrow.columnといった形でアクセス可能。

Seriesをそのまま使う

Seriesはそれ自体イテラブルになっている。DataFrame全体ではなく、特定のcolumnだけが必要ならば、columnを指定してSeriesで回した方が早い。

速度比較

イテレーションの処理速度比較については次のページが詳しい.
note.nkmk.me

条件抽出

pandas.Series.isin(values) -> pandas.Series

Seriesの各要素がvalueに指定した集合またはシーケンスに含まれるかどうかを判定し、bool値のSeriesを返す。

要素カウント

pandas.Series.unique() -> numpy.ndarray

Seriesに含まれる要素のシーケンスを返す。値の重複はなく、Python組み込みの集合のようなもの。

pandas.Series.value_counts() -> pandas.Series

Seriesに含まれる要素とその出現回数をSeriesで返す。indexに要素の値が、valueに要素の出現回数が入る。Seriesだと扱いにくいという場合は、次のメソッドが便利。

pandas.Series.value_counts().to_dict() -> Dict[Any, int]

Seriesに含まれる要素とその出現回数を辞書で返す。

警告

SettingWithCopyWarning

条件やスライスで抽出したDataFrameは、コピーではなく参照を返すことがある。その参照に対して代入など中身を変更する処理を行うとこの警告が発生する。参照ではなく値を返してほしい場面では、pandas.DataFrame.copy()を使ってちゃんとコピーを受け取るようにすれば、警告はでなくなる。

[Python][Matplotlib] 変な形のアトラクターをつくる差分方程式系を適当に探してみる

はじめに

以前、グモウスキー・ミラの写像を作図したときに、差分方程式系のアトラクターはなかなか綺麗に描けるものだとわかった。

cyanatlas.hatenablog.com

そこで、自分でも適当に写像を作って、変な形のアトラクターを描いてみよう、と思ったのがきっかけ。

差分方程式系を作る

二変数の差分方程式系を作れば良いので、つまりは
x_{n+1}=f(x,y)
y_{n+1}=g(x,y)
の形式であれば何でも良いということになる。しかし、大抵の写像は平衡点に収束するか爆発するかである。手を動かしていろいろな写像を作ってみると、「平衡点に収束しないが爆発もしない」という微妙なさじ加減がなかなかむずかしいとわかった。

そこで方針を変えて、既存の写像をちょっと変えて新たな写像を作ることにする。ここではロジスティック写像に変更を加えることにした。ロジスティック写像は次のような一変数の差分方程式。

x_{n+1}=a x_n (1 - x_n)

ロジスティック写像は生物の個体数の変化を記述する数理モデルでもある。その場合、a は一個体が最大で生む子の数を表す。個体数が増えてくると、餌や住み場所がなくなるので、生物は増えにくくなる。この混み合いの効果は負の二次項によって表現されている。

ロジスティック写像をもとに、次のような差分方程式系を作ってみた。

x_{n+1}=a x_n (1 - (x_n + y_n) / 2)
y_{n+1}=b y_n (1 - (x_n + y_n) / 3)

xy もロジスティック写像に似たような格好になっているが、二次項の部分に和 x+y が入っているのがポイントである。後で示すパラメータでは a>b となっている。ロジスティック写像がそうであったように、生物の個体数変化に即して考えると、x は増えるのは早いが他個体から負の影響を受けやすく、y は増えるのはゆっくりだが他個体からの負の影響を受けにくいという構造になっており、それでうまくバランスして2種の生物が共存してくれることを期待した。

作図する

最初に1000回計算した後で、N 回計算して点を打つ方法で作図した。パラメータ ab の値をいろいろいじってみたが、a=3.65, b=3.6 のときが面白かった。

f:id:cyanatlas:20200718005128j:plain
N = 10^3のとき
f:id:cyanatlas:20200718005218j:plain
N = 10^4のとき
f:id:cyanatlas:20200718005246j:plain
N = 10^5のとき
f:id:cyanatlas:20200718005313j:plain
N = 10^6のとき

なんだか奇妙な形の範囲で点が動きつづけていることがわかる。大きく見て、上と下の二つ領域があるようにも見える。N=10^6 のときの図を見ると、真ん中に大きな穴が二つあり、この領域には点が移動しないこともわかる(理屈はわからないが・・・)。

定量的な話をすると、xの値はときどき負の値をとっている。このことは、いま指定したパラメータ(a=3.65, b=3.6)では、この差分方程式を生物の個体数変化として解釈することはできないといういことを意味する。「うまくバランスして2種の生物が共存してくれることを期待した」のだが、少なくともいま指定したパラメータ(a=3.65, b=3.6)に関しては、この企みは失敗したわけである。ロジスティック写像では、x=0 が不安定平衡点になっていて、x が負の値に突入すると  a x(1-x)は常に負になるので、xはどんどん小さくなって-\inftyに発散してしまうはずである。今回の差分方程式系でも同じように-\inftyに発散してしまうかと思いきや、なぜか0 周辺に戻ってきている。

もう少し様子を見るために、次のような三次元の差分方程式に直して作図してみる。

x_{n+1}=a x_n (1 - (x_n + y_n) / 2)
y_{n+1}=b y_n (1 - (x_n + y_n) / 3)
z_{n+1}=x_n + y_n

式中のx_n + y_nという値がポイントになると思ったので、これを各時刻で計算して、第三軸にプロットした。形が理解しやすいよう、z軸で回転したGIFアニメーションを作成した。

f:id:cyanatlas:20200718014654g:plain
N = 10^4のとき
f:id:cyanatlas:20200718025602g:plain
N = 10^5のとき

点の集合はひしゃげた長方形の形をしているようにも見える。z軸で上側の部分と下側の部分で大きく二つに分かれており、その間を行き来するルートが複数あるのが分かる。この図をz軸でつぶして上から見たのが最初の二次元の図であるから、xy平面で見たときに上と下の2領域があるという見立ては間違いではなかったことになる。

安定性解析

平衡点

 (x,y) がともに0でない平衡点なら
x=a x (1 - (x + y) / 2)
y=b y (1 - (x + y) / 3)
が成り立つ。ここで x+y について整理すると
2(1-\frac{1}{a}) = x + y = 3 (1 - \frac{1}{b})
となる。この等式は特殊なパラメータのときでないと満たされない。当然、a=3.65, b=3.6 では満たされない。したがって、ともに0でない平衡点 (x,y) は存在しない。

適当に手計算をすると、平衡点は次の三つであることが分かる。

 x=0, y=3
 x=2, y=0
 x=0, y=0

安定性

ヤコビ行列の固有値を計算すると、

 x=0, y=3 (-3.6, -1.825)
 x=2, y=0 (-3.65, 1.2)
 x=0, y=0 (3.6, 3.65)

となって、いずれの平衡点でも固有値の絶対値が1より大きく、不安定である。

f:id:cyanatlas:20200718023700j:plain
解析に使ったMathematicaのコード

ソースコード

2次元

from tqdm import tqdm

import matplotlib.pyplot as plt
import numpy as np


x0 = 0.5
y0 = 0.5
N = 10000
N_run = 1000
FILENAME = 'data.csv'

a = 3.65
b = 3.6


def func(x, y):
    x_next = a * x * (1 - (x + y) / 2)
    y_next = b * y * (1 - (x + y) / 3)
    return (x_next, y_next)


def calc():
    x, y = (x0, y0)
    with open(FILENAME, mode='w') as f:
        for _ in range(N_run):
            x, y = func(x, y)
        for _ in tqdm(list(range(N))):
            f.write('{:f}, {:f}\n'.format(x, y))
            x, y = func(x, y)
    return


def plot_matplotlib(filename):
    data = np.loadtxt(FILENAME, delimiter=',')
    fig = plt.figure()
    ax = fig.add_subplot()
    ax.scatter(x=data[:, 0], y=data[:, 1], s=0.5, color='C0', antialiased=True)
    fig.savefig(filename, dpi=600)
    plt.close(fig)
    return


def main():
    calc()
    plot_matplotlib(filename='figure_a{:.2f}_b{:.2f}_N{:.1e}.jpg'.format(a, b, N))
    return


if __name__ == '__main__':
    main()

3次元

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D  # NOQA
import matplotlib.animation as animation
import numpy as np
from tqdm import tqdm


x0 = 0.5
y0 = 0.5
N = 10000
N_run = 1000
FILENAME = 'data.csv'

a = 3.65
b = 3.6


def func(x, y):
    x_next = a * x * (1 - (x + y) / 2)
    y_next = b * y * (1 - (x + y) / 3)
    z_next = x + y
    return (x_next, y_next, z_next)


def calc():
    x, y, z = (x0, y0, 0)
    with open(FILENAME, mode='w') as f:
        for _ in range(N_run):
            x, y, z = func(x, y)
        for _ in tqdm(list(range(N))):
            f.write('{:f}, {:f}, {:f}\n'.format(x, y, z))
            x, y, z = func(x, y)
    return


def plot_gif():
    data = np.loadtxt(FILENAME, delimiter=',')
    fig = plt.figure()
    ax = fig.add_subplot(111, projection='3d')

    def plot(angle):
        ax.scatter(xs=data[:, 0], ys=data[:, 1], zs=data[:, 2], s=0.5, color='C0', antialiased=True)
        ax.set_xlabel('x')
        ax.set_ylabel('y')
        ax.set_zlabel('z')
        ax.view_init(10, angle)

    anim = animation.FuncAnimation(
        fig, plot, interval=300, repeat_delay=2000, frames=np.arange(0, 360, 10)
    )
    anim.save('anim_a{:.2f}_b{:.2f}_N{:.1e}.gif'.format(a, b, N), writer="imagemagick")
    plt.close(fig)
    return


def main():
    calc()
    plot_gif()
    return


if __name__ == '__main__':
    main()

CSVの表→はてな記法コンバータ

CSVデータの入力


一番上の行をタイトル行にする
一番左の列をタイトル列にする


変換結果(はてな記法

ここに変換結果が出力されます



使い方

CSV形式の表をはてな記法の表に変換するコンバータを試しに作ってみた(バグあるかも)。

  1. CSVファイルを用意する(Excelファイルからの作り方は後述)
  2. CSVファイルのテキストデータを上のボックスに貼り付ける
  3. チェックボックスで行・列タイトルの有無を設定する
  4. 「変換実行」ボタンを押す
  5. 変換結果のテキストをはてなブログの編集画面に貼る

ExcelファイルからCSVファイルを作る方法

Excelの表を変換したい場合は次の手順でCSV形式のテキストが得られる(Windows10環境の場合)。

  1. Excelファイルを開く
  2. 「ファイル」タブ
  3. 「名前を付けて保存」
  4. CSV UTF-8 (コンマ区切り) (*.csv)」を選択
  5. 保存
  6. 保存したCSVファイルをメモ帳などで開く

関連記事

cyanatlas.hatenablog.com

[Python][Matplotlib] 色の形式を変換する

環境
Windows10 + Python 3.7.6 + Matplotlib 3.1.1

maptlotlib.colorsモジュールには、色を表現する変数を変換する関数が用意されており、なかなか便利そうだったのでまとめてみた。

matplotlib.org

HSV(0~1)からRGB(0~1)へ

hsv_to_rgb(hsv)

  • 引数
    • hsv : HSVに対応する長さ3のシーケンス
  • 返り値
    • rgb : RGBに対応する長さ3のシーケンス

import matplotlib.colors as mcolors

color_hsv = (0, 1, 1)
color_rgb = mcolors.hsv_to_rgb(color_hsv)
print(color_rgb)
[1. 0. 0.]

RGB(0~1)からHSV(0~1)へ

rgb_to_hsv(rgb)

  • 引数
    • rgb : RGBに対応する長さ3のシーケンス
  • 返り値
    • hsv : HSVに対応する長さ3のシーケンス

import matplotlib.colors as mcolors

color_rgb = (1, 0, 0)
color_hsv = mcolors.rgb_to_hsv(color_rgb)
print(color_hsv)
[0. 1. 1.]

RGB(0~1)から16進数へ

to_hex(c[, keep_alpha])

  • 引数
    • c : RGBに対応する長さ3のシーケンスなど
  • 返り値
    • hex : 16進数の文字列

import matplotlib.colors as mcolors

color_rgb = (1, 0, 0)
color_hex = mcolors.to_hex(color_rgb)
print(color_hex)
#ff0000

16進数からRGB(0~1)へ

to_rgb(c)

  • 引数
    • c : 16進数の文字列など
  • 返り値
    • rgb : RGBに対応する長さ3のシーケンス

import matplotlib.colors as mcolors

color_hex = '#ff0000'
color_rgb = mcolors.to_rgb(color_hex)
print(color_rgb)
(1.0, 0.0, 0.0)

Matplotlibで名前の付いている色の一覧の取得

get_named_colors_mapping()

  • 引数:なし
  • 返り値
    • matplotlib.colors._ColorMapping:色の名前と16進数表記が対応づけられた辞書のようなオブジェクト

import matplotlib.colors as mcolors

named_colors = mcolors.get_named_colors_mapping()
print(named_colors['red'])
print(named_colors['xkcd:rust brown'])
#FF0000
#8b3103

メモ:matplotlib.colors._ColorMappingについて

このオブジェクトについて公式ドキュメントで説明を見つけられなかったので、実装を見に行ったところ、次のようなコードを見つけた。ほとんど組み込みの辞書と変わらないオブジェクトと思われる。

class _ColorMapping(dict):
    def __init__(self, mapping):
        super().__init__(mapping)
        self.cache = {}

    def __setitem__(self, key, value):
        super().__setitem__(key, value)
        self.cache.clear()

    def __delitem__(self, key):
        super().__delitem__(key)
        self.cache.clear()

matplotlib.org

ちなみにget_named_colors_mapping()は次のように実装されていた。_colors_full_map変数を返すだけ。_colors_full_map変数ははじめは組み込みの辞書だが、最後に_ColorMappingオブジェクトに変換されている。

_colors_full_map = {}
# Set by reverse priority order.
_colors_full_map.update(XKCD_COLORS)
_colors_full_map.update({k.replace('grey', 'gray'): v
                         for k, v in XKCD_COLORS.items()
                         if 'grey' in k})
_colors_full_map.update(CSS4_COLORS)
_colors_full_map.update(TABLEAU_COLORS)
_colors_full_map.update({k.replace('gray', 'grey'): v
                         for k, v in TABLEAU_COLORS.items()
                         if 'gray' in k})
_colors_full_map.update(BASE_COLORS)
_colors_full_map = _ColorMapping(_colors_full_map)


def get_named_colors_mapping():
    """Return the global mapping of names to named colors."""
    return _colors_full_map

参考リンク

matplotlib.org