KNIMEとか倉庫

KNIMEやEXCELなどの備忘録です。

【Python】dataframeを数字に変換したい。

・お題:pandasのdataframeで、本来数字が入るべきところにエラーの文字列が入っており、無視したいのに数字として処理できない。文字列をNaNに変換し、数字として扱いたい。

 

・データセットを作成する。

import pandas as pd
df=pd.DataFrame({"A":[1,2,3,4,5],
                "B":[1.2, 2.3, 3.4, 4.5, 5.6],
                "C":[6,"seven","eight",9,10],
                "D":[6.5,"n.r",8.9,"n.z","j.j"],
                "E":["a","b","c","d","e"]})

これでdfは以下になる。

 

・各列のデータ型を見てみる。

df.info()で以下が返ってくる。

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5 entries, 0 to 4
Data columns (total 5 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   A       5 non-null      int64  
 1   B       5 non-null      float64
 2   C       5 non-null      object 
 3   D       5 non-null      object 
 4   E       5 non-null      object 
dtypes: float64(1), int64(1), object(3)
memory usage: 328.0+ bytes

CDE列がobjectになっており、数字として認識されていないっぽい。

・非数字を数字(NaNとして欠損値処理)するには、pandasのto_numericメソッドを使う。きちんとしたことは、以下の公式サイトから確認していただきたい。

pandas.pydata.org

henkan=lambda x :pd.to_numeric(x,errors="coerce")

df2=df.apply(henkan)

とすると、df2は以下になる。

・各列のデータ型を見てみる。

df2.info()とすると、以下が返ってくる。

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5 entries, 0 to 4
Data columns (total 5 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   A       5 non-null      int64  
 1   B       5 non-null      float64
 2   C       3 non-null      float64
 3   D       2 non-null      float64
 4   E       0 non-null      float64
dtypes: float64(4), int64(1)
memory usage: 328.0 bytes

CDE列が数字(float64)に変換された。

 

おわり。

 

 

【Python】平均値の棒グラフに個別値もプロットしたい

・お題:A、B、C及びDの4つの試験条件で、実験をしたところ、10個ずつデータを得ることができた。各試験条件ごとに、平均値を棒グラフで示し、ついでに個別値もプロットしたい。

 

・これまでEXCELで似たようなことをやっていたが、jitterの設定ができず、個別値が一列に並んで被ってしまうのが不満だった。EXCELでのやり方が分からなかったので、Pythonで作図してみた。

・まずはデータセットを作成。

import numpy as np
import seaborn as sns
import pandas as pd
a=np.random.normal(10,1,10)
b=np.random.normal(12,1,10)
c=np.random.normal(8,1,10)
d=np.random.normal(14,1,10)
df=pd.DataFrame({"A":a,"B":b,"C":c,"D":d})

dfの中身は以下の通り。

・これの平均値を棒グラフにするには、seabornのbarplotが便利。

sns.barplot(x=df.columns,y=df.mean(),color="skyblue")

・ここに、seabornのstripplotで個別値を追加する。

sns.stripplot(x="variable",y="value",data=df.melt(),color="gray")

デフォルトでjitterがTrueになっているので、勝手に良い感じにばらついてくれる。jitter=Falseにすると、プロットが縦一列に並ぶ。dfをmeltで縦持ちに変換している。

・次に、それぞれの試験条件で、XとYの2群(例えば試験物質)に分かれていたとする。まずはダミーデータを作成する。

import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
import pandas as pd
a=np.random.normal(10,1,10)
b=np.random.normal(12,1,10)
c=np.random.normal(8,1,10)
d=np.random.normal(14,1,10)
e=np.repeat(["X","Y"],5)
df=pd.DataFrame({"A":a,"B":b,"C":c,"D":d,"E":e})

・グラフを作成する前に、データを縦持ちにしておく。今回はそれぞれのデータにXとYを振る。

df_melt=df.melt(var_name="Sample",value_name="Score",id_vars="E")

df_meltは以下になる。

・あとは描画する。凡例は邪魔なので外に出した。

sns.stripplot(x="Sample",y="Score",data=df_melt,color="gray",hue="E", dodge=True)
sns.barplot(x="Sample",y="Score",data=df_melt,color="skyblue",hue="E", dodge=True,ci=None)

#barplotでは勝手に群の代表値で描画してくれるらしい。引数estimator=はデフォルトでmeanになってるっぽい。
plt.legend(labels=["X","Y"], bbox_to_anchor = (1,1))

 

おわり。

【Python】データで計算したい

・お題:DataFrameのデータを使って、集計など、何らか計算したい。

 

・DataFrameが与えられて、何等か計算したいとする。そのような場合には、pandasのapplyメソッドが便利。

・まずはデータセットを作成する。

import numpy as np
import pandas as pd

a=np.random.normal(2,2,100)
b=np.random.randint(1,101,100)

df=pd.DataFrame({"A":a,"B":b})

これでdfの中身は以下になる。

・例えば、変数ごとに合計値を算出したい場合、dfの後にくっつけるとうまくいく。

df.sum()

で以下が返ってくる。

A     190.177445
B    5577.000000
dtype: float64

簡単な処理かつ変数に対して実施するだけなら、これでも問題ない。

・例えば、各変数をすべて二乗したい場合、関数を定義してapplyメソッドに渡すと良さそう。

def func(x):
    return x**2

df.apply(func)

これで、以下が返ってくる。

なんだかできてそう。

・関数を作成するには、defではなくlambdaを使うこともできる。

func = lambda x: x**2

・次に、列または行で集計する場合を考える。今回は変則的に、列または行の最大値から最小値を引いてみる。

func2 = lambda x:max(x)-min(x)

として関数を定義し、

df.apply(func2)

とすれば、

A     7.826449
B    99.000000
dtype: float64

が返ってくる。

列ごとに最大値から最小値を引いた値が返ってきた。

・行ごとに同じ処理をしたいのであれば、引数にaxis=1を追加する。

df.apply(func2,axis=1)

とすると

0     56.235408
1      2.688923
2     56.958441
3     29.874747
4     46.927073
        ...    
95    92.124378
96    18.656672
97    61.557063
98    52.953695
99    85.004783
Length: 100, dtype: float64

が返ってきた。

・これを新たな変数としてdfに追加したいのであれば、列名を指定してそのまま放り込めば良いみたい。

df["C"]=df.apply(func2,axis=1)

とすると、

が返ってきた。確かにC列がくっついている。

・次に、文字列の変数列Dが含まれるdataframeの集計を考える。

d=np.repeat(["a","b","c","d"],25)

df2=pd.DataFrame({"A":a,"B":b,"D":d})

df2は以下になる。

・df2にapply関数でfuncを適用すると、型のエラーが返ってくる。D列の二乗が計算できないみたい。df2.drop("D",axis=1)で数字以外の列を落としてから計算させても良いけれど、少し面倒に感じる。

・数字でないものをNaNに変換してから、funcすれば、エラーは一応回避できる。数字でないものをNaNに変換するには、pd.to_numeric(対象, errors="coerce")を使えばよいので、これを関数化してapplyでdataframe全体に適用すれば良い。

nume=lambda x :pd.to_numeric(x, errors="coerce")

df2.apply(nume)

D列が絶滅した。仮にD列に数字が入っていれば、きっと生き残っただろう。この状態ならfuncやfunc2を適用してもエラーが出ない。

 

・dataframe全体から非数字を潰して集計するのは使えそう。

 

おわり

【Python】複数のヒストグラムをいい感じに並べたい

・お題:いろいろなヒストグラムを分かりやすく並べて描画したい。

 

ヒストグラムは分布をみるのに便利だけれど、たくさん描くと見づらくなってしまう。そこで、良い感じに複数のヒストグラムを並べて描画できないか、少し調べてみた。

 

・とりあえず、データセットを作成する。

import numpy as np
import pandas as pd

 

a=np.random.normal(0,1,100)
b=np.random.normal(-10,1,100)
c=np.random.normal(0,3,100)
d=np.random.normal(5,0.5,100)

X=list(np.repeat("x",80))
Y=list(np.repeat("y",20))

z=X+Y

df=pd.DataFrame({"A":a,"B":b,"C":c,"D":d,"Z":z})

でdfの中身は以下になる。Zはなんとなく作成したラベルで、ラベルごとの各変数の分布も見たい場合の作図にちょっと使う。

 

・pandasの機能でヒストグラムを作図する。

df.plot.hist(bins=20,alpha=0.5)

透けさせているので、少し見やすくなっている。用途によってはこれで十分かも知れない。

・調べてみたところ、joypyというライブラリがヒストグラムを並べるのに便利らしい。pip install joypyでjoypyをインストールし、さっそく使ってみた。

import joypy
joypy.joyplot(df)

ちょっとずらしてヒストグラム(?)を描画してくれた。先ほどよりも見やすい気がする。

・ちなみに、seabornでも同じようなことができるらしいが、joypyの方が簡単な雰囲気がある。

seaborn.pydata.org

・次に、joypyでZがxの群とyの群の分布を描き分ける。

joypy.joyplot(df, by="Z", alpha=0.5, legend=True)

シンプルな記述で、見やすいグラフを描くことができたように思う。

 

おわり

 

 

 

 

 

 

【Python】ベン図を描きたい

・お題:ベン図を描きたい。細かい設定なしで、集合を放り込むだけで、勝手にベン図を描いてくれると尚良い。

 

・少し調べてみたところ、ベン図を描画できるライブラリがあった。matplotlib-vennというライブラリ。pip install matplotlib-vennでインストールする。

 

・ベン図を描く前に、知っておかなければならないのが、Pythonでの集合の扱い方。集合なのだから、要素が集まったものなのだけれど、では引数としてどうやって集合を渡せば良いのか。リスト型ではダメらしく、集合の型(set)にする必要があるみたい。

Pythonで、set型は{}(波になっている括弧。正式名称は知らない)で表現される。{1, 2, 3, 4, 5}など記載すれば、そのままsetとして扱ってくれる。辞書っぽいが、辞書ではない。

・リスト型をset型に変換するには、set関数を使えば良い

a=[1,2,3,4,5]#aはリスト型

a=set(a)

とすれば、aは{1, 2, 3, 4, 5}(set型)になる。

ちなみに、setは集合なので、重複を許さないらしい。

b=[1,2,3,3,3,3,3,3,4,5,5]

b=set(b)

とすれば、bは{1, 2, 3, 4, 5}になる。重複していた3や5が一つの要素として纏められた。

・以降では、サンプルデータとしてset型を直接使うが、リスト型から渡す場合にはset型に変換しつつ渡す。

 

・まずは、2つの集合でベン図を作成する。

from matplotlib_venn import venn2
import matplotlib.pyplot as plt

venn2(subsets=[{1,2,3,4,5,6,7,8,9,10},{3, 6, 9, 12,15,18}], set_labels=('A', 'B'))#集合のリストを引数subsetsに渡す。

 

・venn2が2つの集合なのだから、venn3なら3つの集合のベン図が描ける。

from matplotlib_venn import venn3
import matplotlib.pyplot as plt

venn3(subsets=[{1,2,3,4,5,6,7,8,9,10},{1,3,5,7,9,11,13,15,17,19}, {3,6,9,12,15,18}], set_labels=("A", "B","C"))

・ちなみに、"subsets="を省略してもデータは認識されたし、"set_labels=("A", "B","C")"は無くてもグラフ自体は描くことができるみたい(どれがどれか分からなくなって混乱しそうだけれど)。

 

・こういう風にベン図を描けるのは新鮮。

 

おわり

 

【Python】dataframeの列名やインデックスをまとめて変えたい。

・お題:DataFrameを作成したが、列名やインデックスに問題がある。置換したり、一括で変えたり、接尾辞を加えたり、いろいろとうまいこと変更したい。

 

・とりあえず、dataframeを作成する。

import pandas as pd
dic={"A":[1,2,3],
    "B":[4,5,6],
    "C":[7,8,9]}
df=pd.DataFrame(dic,index=["a","b","c"])

これで、以下のdfができた。列名がA,B,Cで、indexがa,b,cになっている。

 

・indexのbをsecondに、cをthirdに変えたい場合、名称変換の対応を示す辞書を作成し、renameメソッドに渡せば良い(以下)。

new={"b":"second",
     "c":"third"}

df.rename(mapper=new)

・同じように、列名のAをalphaに、Bをbetaに変えたい場合、名称変換の対応を示す辞書を作成し、renameメソッドに渡す。ただし、axis=1を追加し、列名であることを明示する(以下)。

new2={"A":"alpha",
    "B":"beta"}

df.rename(mapper=new2,axis=1)

これで出力は、

・引数mapperで辞書を渡し、引数axisで列名の話かindexの話か渡す訳だけれど、列名とindexを両方変えたい時だってある。そういう場合は引数columnsと引数indexに辞書を渡せば、行列をそれぞれの引数だけで処理できる(最初からこちらだけで良かったかもしれない……)。

 

・列名やindexを、「どれをどれに」ではなく、一括で全部変えたい場合には、set_axisを使う。

df.set_axis(["first","second","third"])

df.set_axis(["alpha","beta","gamma"],axis=1)

変更後の列名またはindexの一覧をリストで渡すことになるが、これが変更前の列数やindex数と違うとエラーが返るっぽい。

 

・列名に何かをくっつけたい場合は、add_prefixまたはadd_suffixをくっつける。

df.add_prefix("column_")

df.add_suffix("_column")

ちなみに、prefixは接頭辞のことで、suffixは接尾辞のことらしい。一つ賢くなった。

・同じ目的の別解として、.formatを使うこともできる。

df.rename("{}_column".format,axis=1)

で同様の結果を得られるし、axis=1を指定しなければindexに接尾辞を付け足すことができる。こっちの方が便利かも。

 

・ちなみに、dataframeから列名やindexを引っ張ってくるには、.columnsや.indexを付ければ良い。

df.index

でIndex(['a', 'b', 'c'], dtype='object')

df.columns

でIndex(['A', 'B', 'C'], dtype='object')

が返ってくる。

 

終わり

 

【Python】表を縦長にしたい

・お題:マトリックスになっている表を、リスト形式に変形したい。

 

・表の形式として、マトリックス形式とリスト形式というやつがあるらしい。

マトリックス形式が以下の形式で、プレートマップのような感じ。

対して、それぞれの要素を縦に並べたような表を、リスト形式と呼ぶっぽい(下図)。

マトリックス形式の方が視覚的に情報を把握しやすい反面、自由度が低く、無駄が多い場合がある。リスト形式の方がデータを取り扱う場合には便利なことが多い。ということで、表を縦長に成型する方法を調べた。

・プレートマップを意識し、以下のデータの入ったcsvファイルをマトリックス形式で読み込む。

  1 2 3 4 5 6
A 2.3 7.1 8.1 6.9 3.3 8.1
B 7.3 3.5 4.4 4.6 7.4 6.5
C 5.4 9.4 1.6 6.6 0.4 7.1
D 9.9 2.9 2.9 2.9 1.5 7.3

import numpy as np
import pandas as pd

df=pd.read_csv("xxx.csv",index_col=0)

とすると、dfの中身は以下になる。

・これを縦長にするには、stack()を使う。

df2=df.stack()

とすると、df2の中身は、以下になる。

なんだか気持ち悪いと思ったら、df2と名付けたにもかかわらず、Series形式で、しかもマルチインデックスが返ってきた。

・マルチインデックスを解除するには、.reset_index()すれば良い。

df3=df2.reset_index()

とすれば、df3の中身は、

となる。これで目的は達した。

・せっかくなので、縦長の表をマトリックスに戻す。手順を逆に辿れば良いので、まずはマルチインデックスにする。

df4=df3.set_index(["level_0","level_1"])

これでdf4は

になる。

・これを単純にunstackすると、マルチカラムが鬱陶しいことになる(下図)。

df4.unstack()

droplevelでデータのカラム名(0)を除去すると、ちょっとマシになる。

df4.unstack().droplevel([0],axis=1)

一度振られたlevel_0の消し方はよく分からなかった。

・次に、複数の条件から定義される投与群×投与日という形のマルチインデックスの表(下表)を縦長にする。

Compound Dose Day0 Day1 Day2
A 1 25.9 25.9 26.1
A 3 25.4 25.4 26.3
B 1 25.7 25.5 25.5
B 3 25.7 25.5 25.1

・といっても、読み込みの際にマルチインデックスを設定すれば良いだけみたい。

df=pd.read_csv("xxxx.csv",index_col=[0,1])

dfの中身

df2=df.stack().reset_index()

df2の中身

・ちなみに、マルチインデックスに戻してunstackしてdroplevelすると、元のマトリックスぽくなる。

df3=df2.set_index(["Compound","Dose","level_2"]).unstack().droplevel([0],axis=1)

df3の中身

 

・なんだか腑に落ちないけれど、一応それっぽい形にはなった。

 

おわり。