週1で書き続けた、2019年のブログ活動を振り返る

この記事はwrite-blog-every-week Advent Calendar 2019 - Adventarの21日目です。 前日の投稿はUdomomoさんによる 約1年間ブログを続けられている理由を振り返る - りんごとバナナとエンジニア です。

今年のブログの総括・振り返りの記事です。
write-blog-every-week Advent Calendar 2018 - Adventarで書いた、2018年の振り返り記事はこちら。

linus-mk.hatenablog.com

……読み返してみると、去年は時系列形式で書いたんだな。
今年は何か展開があったわけでもないので、時系列ではなく、主題ごとに書く。

write-blog-every-weekとは何か

ブログを毎週書く人たちによるSlackグループである。 毎週の月曜から日曜までの間に1記事を書く必要がある。

詳しくはこの記事を参照。

発端:勢いで週一ブログ書くslackグループを作った - もがき系プログラマの日常

PV

月間PV10000に行きそうで行かない(はてなアクセス解析。以下同じ。Google Analyticsだと月間8000前後)。 10月は9700くらいまで行ったけど、以降はGoogleの検索順位が下がったのか減ってしまった。 PV10000の通知が来るのを見てみたいものだ。

Google Analyticsスクリーンショットは以下の通り。*1 今年の後半から伸びが鈍化しているように見える。

f:id:soratokimitonoaidani:20191221232222p:plain

PV 1位から5位まで

2019年の1月から11月までのアクセス1〜5位は、次のとおりである。パーセンテージは全体に占める割合を示す。

1位 [pandas]特定の条件を満たす行を削除する - 子供の落書き帳 Renaissance 30.22%
2位 python環境が壊れた(おそらくcondaとpipの競合が原因) - 子供の落書き帳 Renaissance 27.94%
3位 続・機械学習モデルを解釈する方法 SHAP value - 子供の落書き帳 Renaissance 5.81%
4位 pandasのSettingWithCopyWarningを理解する (1/3) - 子供の落書き帳 Renaissance 5.13%
5位 画像セグメンテーション用のアルゴリズム、u-netについて簡単に説明する - 子供の落書き帳 Renaissance 4.82%

上位2記事で全体の過半数と、大きく偏った分布になっている。

今年書いた記事がアクセス1位になってるのは良いのかもしれない。
しかし、これは全然力を入れずに、ちょっとしたメモ程度に書いた記事なんだよね……うむむ。

PV狙いの極北は「how to記事」なのではないか

確固たる理由があるわけではないが、いろいろな記事を読んだり書いたりするうちに、そんな風に考えるようになった。
結局、みんなが検索するのって「~~したいけど、方法が分からん」という場合が圧倒的に多いんじゃないか?
だからPV1位が「pandasで条件を満たす行を削除する方法」の記事なのだろう。

なお、「how to」の部分集合として、「how to resolve an error」すなわちエラーを解決するための記事がある。
エラーが出てよく分からなかったら、エラーメッセージで検索することがあるだろう。 あれである。

俺のブログでいうと、settingwithcopywarningの記事がそれに該当する(エラーではなく警告だが)。 アクセス解析を見ると、警告メッセージの一部で検索してこの記事に辿り着く人が多数見られる。

一方で、「What I did」と俺が勝手に呼んでいる、何かをしたよという記事は読まれにくいように思う。 例えば、カンファレンスに参加した、技術書典に参加した、技術書を読んだ、などなど。
(本を書いた/カンファレンスで発表した のような、希少性のあるWhat I did だと少し違うかもしれない。)
日々カンファレンスが開催され、技術書が出版され、アプリがリリースされる状態で、特定のものが注目され続ける期間は長くはない。 警告の意味が分からなくて警告メッセージで検索してくる人は(pandasの挙動が変わらなければ)3年後にもいるだろうが、今年のPyConJPの感想を知りたい人は3年後には多くないだろう。

仮に俺の推測が正しいならば、アクセスのためと割り切るならばHow toに該当する記事ばかり書き続ければ良いように思える。
しかしそうすると、「これを書いているのはどういう人なんだろう」という、 筆者のキャラクターが極めて希薄になる。 なぜなら、How toというのは誰がやっても同じ結果になるからこそHow toなのであって、 誰が書いても似た内容になってしまう傾向が強いからだ(もちろん書き方の違いはあるが)。 これが自分の経験(What I did)を語った、例えば「アプリがヒットするまでに取り組んだこと」の記事だと、仮にタイトルは同じでも中身は個々人で違ってくるだろう。
自分のオリジナリティが発揮しづらい記事を書き続けるのはなかなか嫌なので、How toの記事だけというのもあまりやりたくないなというのが実際の心境である。

実際には締切に追われて書いてる。

と、まぁ、PVのことばかり書いたけれど、実際にはそこまでPVのことなど考えずに書いてる。 (というか、週1で締め切りが来るので、締め切りに追われてあんまりPVとか考えてる暇は無かった。)

未完成のまま締め切りに追われて出した記事

白状すると、「あとで書く」みたいな語句を書いたままの記事がそれなりにある。 これはすなわち、未完成だが締め切りが来たので公開せざるを得なかったということである。

自分の記事一覧を見ながらザッと挙げてみると、以下のようになる。

……あれ?今年後半の記事ばっかりじゃない???
この状態が続くようなら、週1更新に拘泥するのも考え直さねばならないなぁ。

PVの多いもの以外で、頑張ったと思う記事

linus-mk.hatenablog.com pandasのdtype周りでつまづいたので、「一番詳しくて正確なのは公式ドキュメントでしょー!」と言って関連部分を翻訳した。

linus-mk.hatenablog.com 競技プログラミングでよく出題される累積和の変形版について。抽象的な数学の話なので読むのには苦労するだろうけど、このネタで書かれた記事を見たことがないので、自分らしさが出せたかなと。

linus-mk.hatenablog.com StackOverflowの関連する記事も見て、類似の関数との相違点・分かりにくい挙動についても記述した。


振り返りはこんなところでおしまい。来年のやりかたはもう少し考えよう。

Django Girls チュートリアルには足りないものが1つある

タイトルを「Django Girls チュートリアルに欠けている、たった一つのこと」みたいな感じの惹句にしようかと思ったけど、これは不正確だなと思ったのでやめた。

f:id:soratokimitonoaidani:20191215234556p:plain

Djangoの初心者がDjango Girlsチュートリアルを通してやってみたけど、モヤモヤする部分があり、考えるうちにこれが足りないんじゃないかと気づいたので書き留めておく。 難しくて分からないと感じた初心者がいたら、もしかしたら参考になるかもしれない。

筆者の状況:

Django Girlsチュートリアルが終わってからDjango公式チュートリアルに取り掛かったが、 Django Girlsチュートリアルのほうを先にやったのは正解だったと断言できる。 Django Girlsチュートリアルは、最初にやる教材としては他よりも良いが、まだ改善点もあるよね、という趣旨だ。

Django Girlsチュートリアルには「ページが表示されるまでの概要」の説明が足りない

チュートリアルの作業指示はかなり明確に書かれていて、あまり迷うことは無かった。 指示する通りに実行すれば、無事にアプリを作ってページを表示させることができる。 それは間違いない。
では何がいけないのだろうか。

チュートリアル「ここをこう書き換えなさい」
俺「はい」
チュートリアル「この状態で実行すると、このようなエラーになります」
俺「はい」
チュートリアル「このページを表示するにはビューを作る必要があります」
俺「……え、そうなの?」
チュートリアル「では次に、このファイルをこう書き換えなさい」
俺「はい」

という調子でコーディングをしていた。
コードを書きながら、「譬えて言えば以下のようなテキストだけを渡されて目的地まで歩かされる感じだなぁ」と思った。

100m行くと、コンビニがあるので、そこを右に曲がりなさい。
その後、3つ目の交差点に郵便局があるので、そこを左にまがりなさい。
その後、2つ目の交差点でスーパーマーケットがあるので、そこを右に曲がりなさい。
……

目的地には着ける。 チュートリアルの通りに実装すれば、動くものはできるのと同じだ。 だけど何かが足りない。それは、現在地から目的地までの全体を表す地図だ。 URLを入力してからページが表示されるまでに、views.pyが何をやって、models.pyが何をやっているのか、それが分からなかった。

単体の動作を変更しても、全体の理解にはつながらない

上記の書き方だと「何も考えずに脳みそを空っぽにしてコードを書いていた」ように見えるので、少し説明しておく。 一応これでも、チュートリアルをただ単純になぞるだけでは無く、いろいろ試してはみた。 例えば単語を打ち間違えたら、わざとtypoしたまま動かしてみてどうなるか見てみたり、色を変えてみたりした。

他の例を挙げると、チュートリアルの終盤のDjangoフォーム · Django Girls Tutorialでは「"glyphicon glyphicon-pencil"」というのが登場する。 「glyphicon」で検索すると、どうやらこれはアイコンのセットであるらしい。 じゃあComponents · Bootstrapでアイコンの一覧を見て、"glyphicon glyphicon-pencil""glyphicon glyphicon-book"に変更してみよう、確かに鉛筆のアイコンが本のアイコンに変わったね、くらいは試してみた。

これらの行動が無意味だったとは言わない。 しかし、上記は個々の部分を単純に入れ替えて変更しているに過ぎない。 いくら個々のパーツを変えても、全体がどういう仕組みで動いているかは分からないのだ。 そして全体を説明してくれる図は全然無かったのだ。

初学者・初級者向け Django の学習ロードマップ - akiyoko blog というdjango初心者向けの記事がある。 よく見て学習の参考にさせてもらった。
この記事には以下のような記述がある。

その中でも「Django 公式チュートリアル」から入る初学者の方が多いと思いますが、少し注意が必要です。というのも、公式チュートリアルには図が一切なく、全体像が把握しづらいです。

「公式チュートリアルには図が一切ない」のは正しい。
ただし、実はDjango Girls チュートリアルの方にも図がほとんどない。 画像ファイルはほとんどが作成したアプリケーションのスクリーンショットだ。
図と言えるのは2枚だけで、毛玉が絡まったような「Webの図示」だけだ。 理解の助けになるのかならないのかわからない謎の毛玉を載せるくらいなら、各Pythonファイルの関連を示した図を入れてくれよ!
個人的には、Django Girls チュートリアルの説明も「全体像が把握しづらい」ものだった。

それではどういう図があればよいか?

Djangoを最速でマスターする part1 - Qiita
このページの「リクエストを受けてからレスポンスを返すまで」というセクションに非常に分かりやすい図があった。これだよ、俺が求めていたのは!
これがあれば各pythonファイルを書いてるときに「これはこういう部分なんやなー」と思いながらコーディングできただろう。


追記(2020年1月15日)
Django チュートリアル 難しい」で検索すると出てくる記事が、まさに俺と同じことを書いていた。全く同感である。

少し長くなるが、引用しておく。

Tutorialに言われるがままモデルをつくり有効化しAPIと戯れてみたが...わからん。ここをこうしろと言われて(それでもミスすることはあるが)書いてあるとおりに反応が帰ってきて、だからオレは何ができるようになったというの。
(中略)
初心者にはひと目でわかる全体像が必要である
これで今自分がやっていることの意味がわかる。今自分がどこにいて、どこに行こうとしているのかわかる。ざっくりと全体像がわかることが初心者には大切だ。Tutorialでポチポチ入力するのは、暗い森のなかを親切に手を引かれて案内されてる感じ。ゴールまではたどり着くんだろう。でもその後は?放り出されて何をしていいかどこへ行けばいいのかわからない。地図(全体像)があったら、手探りでも確認しながら自分で歩いていける気がする。この感覚が欲しかった。
https://qiita.com/soh506/items/63a656f26ee24014c04b

全体像すなわち地図が必要だという比喩まで一致していてビックリである。俺と同じ感想を持っていた初心者がいることが確認できた。

iPadでChromeからpdfをGoodnotesに送る方法

iPadはPDFで論文や電子書籍を読むのに使えるけど、なぜかChromeで開いたPDFファイルをGoodNotesに送れないという話。

環境:
iPad Pro(11インチ、2018年モデル)
GoodNotes 4

GoodNotesの4→5が劣化だから4を買った方がいいよ、という意見を見かけたので4を買って、アップデートせずそのまま。

Google Chromeでpdfファイルを開いた状態で、共有ボタン(右上にある、四角に上向き矢印のアイコン)を押しても、アプリ一覧の中に「GoodNotes」が出てこない。
なぜかSafariだと、pdfファイルを開いた状態で、共有ボタン(四角に上向き矢印)を押すと、「GoodNotesにコピー」というボタンがあり、それを押すとGoodNotesで開ける。

ブラウザは専らChromeに頼っているから、Safariなんて使いたくないのにー。普段使っているChromeでは開けない。
一体何でなんだー。
誰か教えてー。

……と思ったけど、色々いじっているうちにChromeからGoodNotesに送る方法を発見した。 pdf文書を適当にタップ→右下に出てくる「次で開く…」を押す。
すると「GoodNotesにコピー」というボタンが出てくる!何だこの謎仕様は!!

iPad Proは電子書籍を読むのに良いなと思って買ったけど、まだあまりうまく活用できてないなぁ。

python setの和集合・積集合の計算に、and/orは使えない

タイトルの通りである。 python setの和集合・積集合の計算にand/orは使えない。

ミスってからすぐ気づいたので大きな手戻りにはならなかったが、備忘録として今後忘れないように書き留めておく。

setに対してand/orを使うと何が起きるか

Pythonのバージョン:Python 3.7.4

PowerShellpythonと打ってインタプリタ形式で起動した。

>>> x = {1, 2, 3}
>>> y = {2, 3, 4}
>>> x and y
{2, 3, 4}  # 集合のand、積集合ならば{2, 3}になるはず
>>> x or y
{1, 2, 3}  # 集合のor、和集合ならば{1, 2, 3, 4}になるはず

上記の例では、意図したとおりに動いていないのが明らかである。 しかし、実際に作業をしていたときは、両方の集合が色々と演算した結果だったので、返ってきた集合が意図通りでないということがすぐには分からず、その後コードを書き進めて「あれ?何かおかしくないか?」となった。 幸い、「python set 積集合」で検索してすぐに正解にたどり着いた。

正しい書き方

和集合には | 、積集合には& を使う。

>>> x = {1, 2, 3}
>>> y = {2, 3, 4}
>>> x & y  # 集合のand、積集合
{2, 3}
>>> x | y  # 集合のor、和集合
{1, 2, 3, 4}

他にも、差集合、対称差集合の演算子もある。詳しくは公式ドキュメントを参照のこと。
組み込み型 — Python 3.8.0 ドキュメント

仕組み

では、何でand/orを使ったら上記のような挙動になったのか?この理由は、以下の2点によって説明できる。

  • x and yの仕様は「x が偽ならx, xが真ならばyを返す」である
  • set型(集合)のオブジェクトの真偽値判定は、空集合でないならTrue、空集合ならFalseを返す

1点目の公式ドキュメントはここ。
組み込み型 — Python 3.8.0 ドキュメント
PyQのブログによる、もう少し詳しい解説はここ。
Pythonのandとorはif文以外でも使える?andとorの動作が面白いという話をします - Python学習チャンネル by PyQ

2点目の公式ドキュメントはここ。
組み込み型 — Python 3.8.0 ドキュメント
ここを見ると、bool()を使えば真偽値を判定した結果(ブール値にキャストした結果)が返ってくるのね。
組み込み関数 — Python 3.8.0 ドキュメント
int()はよく使うけど、bool()は違和感があるな……

やってみよう。

>>> bool({1, 2, 3})
True
>>> bool(set())
False

1つ以上の要素の入ったsetはTrue、空のsetはFalseになっている。 ({}と書くと、空のsetではなく空の辞書になってしまう。空のsetはset()と書く。)

以上の2点を踏まえて、もう一度最初の例に戻ろう。

>>> x = {1, 2, 3}
>>> y = {2, 3, 4}
>>> x and y 
{2, 3, 4}  # 最初にxの真偽値を評価する。xは空でないsetなのでTrueである。したがってyを返す。
>>> x or y
{1, 2, 3}  # 最初にxの真偽値を評価する。xは空でないsetなのでTrueである。したがってxを返す。

文法規則に従っているから、言われてみればその通りだけど、初見だとビックリするなぁ。 「おいおい、そこで使ってるand/orは和集合・積集合の演算子じゃない、論理演算子だぞ。大丈夫か?」という警告をしてくれるわけでもない。仕様をちゃんと理解して気を付けよう……

pandasのデータ型、dtypeについて 公式ドキュメントを翻訳した

この記事は何?公式ドキュメントの(個人的な)翻訳だよ

Essential basic functionality — pandas 1.4.1 documentation
(2022年3月5日追記:pandas URLが変更されていてリンク切れになっていたので最新のURLに変えましたが、下記は1.0.3時点の公式ドキュメントの翻訳です。)

pandasの型であるdtypeについての公式ドキュメント(v 1.0.3時点)を翻訳した。
内容は、pandasのSeriesやDataframeに関して

  • dtypeの基本事項
  • 型を判定・確認する方法、
  • 他の型と結合した時のアップキャスト
  • astype()関数を使って指定した型に変更・変換する方法
  • infer_objects()、to_numeric()関数を使って指定した型に変更・変換する方法
  • 落とし穴(nanが入ると勝手にdtypeが変わってしまう仕様について)

などである。
(元は公式ドキュメントだが、この翻訳は誰かが公認したわけではなく、個人的・非公式のものである。念のため。)

f:id:soratokimitonoaidani:20191123180503p:plain

参考になる書籍が少ないよ

pandasを使ってデータ処理をしたいときに、データの型を操作することが主目的だという場合は少ないだろう。 しかし、型システムは根幹の部分で必ず使われているため、型をどう扱っているかの理解が必要になる場合もある。 pandasを使う上で避けて通れない話だが、文献を見ても意外と型の記述は少ない。

pandasを作ったWes McKinneyが書いた「Pythonによるデータ分析入門 第2版 ―NumPy、pandasを使ったデータ処理」にも、dtypeの話はあまり載っていなかった。 インプレス社の「Pythonデータ分析/機械学習のための基本コーディング! pandasライブラリ活用入門」の第7章「データ型の概要と変換」では、まるまるdtypeの話をしている。 pp150〜159の10ページあり、私が見た中で最も分量の多い記述である。

書籍を見てもまとまった記述は少ないし、pandas公式ドキュメントを参考にするのが一番だろう。 ということで、公式ドキュメントの該当部分を翻訳した。


dtypes

pandasはほとんどの部分において、Seriesと、DataFrameの個々の列に対して、NumPyのarrayとdtypeを使用している。 NumPyはfloat, int, bool, timedelta64[ns] and datetime64[ns]をサポートしている。 (NumPyは、タイムゾーンの区別のあるdatetimeをサポートしないことに注意。)

pandasやサードパーティのライブラリは、いくつかの点においてNumPyの型システムを拡張している。 このセクションでは、pandasの内部で実施されている拡張について説明する。 pandas上で動作する、自作の拡張を書く方法については、[拡張データ型]を参照のこと。 データ型の拡張をしたサードパーティ製ライブラリの一覧については、[拡張データ型]を参照のこと。

以下の表ではpandasの拡張型を全て列挙している。それぞれの型についての詳細な説明は、各ドキュメントを参照のこと。

Kind of Data Data Type   Scalar  Array   Documentation
tz-aware datetime   DatetimeTZDtype Timestamp   arrays.DatetimeArray    Time zone handling
Categorical CategoricalDtype    (none)  Categorical Categorical data
period (time spans) PeriodDtype Period  arrays.PeriodArray  Time span representation
sparse  SparseDtype (none)  arrays.SparseArray  Sparse data structures
intervals   IntervalDtype   Interval    arrays.IntervalArray    IntervalIndex
nullable integer    Int64Dtype, … (none)  arrays.IntegerArray Nullable integer data type

pandasは文字列を格納するのに、objectというdtypeを用いる。

最後に、任意のオブジェクトは、object dtypeを使えば格納することができる。 しかしこのやり方は可能な限り避けるべきである。 (パフォーマンスが悪くなるため、そして、他のライブラリやメソッドとの相互運用性のためである。オブジェクトの変換 の節を参照。)

DataFrameにはdtypesという便利な属性があり、dtypesは各行のデータ型が格納されたSeriesを返す。

In [328]: dft = pd.DataFrame({'A': np.random.rand(3),
   .....:                     'B': 1,
   .....:                     'C': 'foo',
   .....:                     'D': pd.Timestamp('20010102'),
   .....:                     'E': pd.Series([1.0] * 3).astype('float32'),
   .....:                     'F': False,
   .....:                     'G': pd.Series([1] * 3, dtype='int8')})
   .....: 

In [329]: dft
Out[329]: 
          A  B    C          D    E      F  G
0  0.035962  1  foo 2001-01-02  1.0  False  1
1  0.701379  1  foo 2001-01-02  1.0  False  1
2  0.281885  1  foo 2001-01-02  1.0  False  1

In [330]: dft.dtypes
Out[330]: 
A           float64
B             int64
C            object
D    datetime64[ns]
E           float32
F              bool
G              int8
dtype: object

Seriesオブジェクトについては、dtype属性を用いる。

In [331]: dft['A'].dtype
Out[331]: dtype('float64')

pandasオブジェクトの一つの列の中に、複数のdtypeからなるデータがある場合 その列のdtypeは、全てのデータの型が格納できるような型が選ばれる(object型が最も汎用的である)。

# 6. が浮動小数点数なので、整数は浮動小数点数に変換される
In [332]: pd.Series([1, 2, 3, 4, 5, 6.])
Out[332]: 
0    1.0
1    2.0
2    3.0
3    4.0
4    5.0
5    6.0
dtype: float64

# 文字列のデータが含まれる場合は、強制的に"object" dtypeになる
In [333]: pd.Series([1, 2, 3, 6., 'foo'])
Out[333]: 
0      1
1      2
2      3
3      6
4    foo
dtype: object

DataFrame.dtypes.value_counts()を呼び出すと、DataFrameの中にそれぞれのデータ型の列がいくつあるかが分かる。

In [334]: dft.dtypes.value_counts()
Out[334]: 
bool              1
object            1
int8              1
float64           1
float32           1
int64             1
datetime64[ns]    1
dtype: int64

数値型のdtypeは、伝播し、DataFrameの中で共存できる。 (dtypeキーワード、渡されたndarray、渡されたSeriesのいずれかを通じて)dtypeが渡されたならば、 DataFrameの操作の中でdtypeは保存される。 さらに、異なる数値型は結合されない(訳注:ある列と別の列が独立なので、別の型で共存できる、ということを言っているのだろう) 以下の例を見れば、動作の一端が分かるだろう。

In [335]: df1 = pd.DataFrame(np.random.randn(8, 1), columns=['A'], dtype='float32')

In [336]: df1
Out[336]: 
          A
0  0.224364
1  1.890546
2  0.182879
3  0.787847
4 -0.188449
5  0.667715
6 -0.011736
7 -0.399073

In [337]: df1.dtypes
Out[337]: 
A    float32
dtype: object

In [338]: df2 = pd.DataFrame({'A': pd.Series(np.random.randn(8), dtype='float16'),
   .....:                     'B': pd.Series(np.random.randn(8)),
   .....:                     'C': pd.Series(np.array(np.random.randn(8),
   .....:                                             dtype='uint8'))})
   .....: 

In [339]: df2
Out[339]: 
          A         B    C
0  0.823242  0.256090    0
1  1.607422  1.426469    0
2 -0.333740 -0.416203  255
3 -0.063477  1.139976    0
4 -1.014648 -1.193477    0
5  0.678711  0.096706    0
6 -0.040863 -1.956850    1
7 -0.357422 -0.714337    0

In [340]: df2.dtypes
Out[340]: 
A    float16
B    float64
C      uint8
dtype: object

デフォルトの動作

デフォルトでは、整数の型はint64、浮動小数点数の型はfloat64であり、これはプラットフォーム(32ビットか64ビットか)に関係ない。以下のコードの結果は、全てint64 dtypeである。

In [341]: pd.DataFrame([1, 2], columns=['a']).dtypes
Out[341]: 
a    int64
dtype: object

In [342]: pd.DataFrame({'a': [1, 2]}).dtypes
Out[342]: 
a    int64
dtype: object

In [343]: pd.DataFrame({'a': 1}, index=list(range(2))).dtypes
Out[343]: 
a    int64
dtype: object

ただし、NumPyが配列を作るときには、型の選択はプラットフォームに依存するので注意。 以下のコードの結果は、32ビットのプラットフォームで実行した場合にはint32となる。

In [344]: frame = pd.DataFrame(np.array([1, 2]))

アップキャスト

型は、他の型と結合した場合にアップキャストされる可能性がある。すなわち、現在の型よりも上位の型に(例えばintからfloatに)変換される。

In [345]: df3 = df1.reindex_like(df2).fillna(value=0.0) + df2

In [346]: df3
Out[346]: 
          A         B      C
0  1.047606  0.256090    0.0
1  3.497968  1.426469    0.0
2 -0.150862 -0.416203  255.0
3  0.724370  1.139976    0.0
4 -1.203098 -1.193477    0.0
5  1.346426  0.096706    0.0
6 -0.052599 -1.956850    1.0
7 -0.756495 -0.714337    0.0

In [347]: df3.dtypes
Out[347]: 
A    float32
B    float64
C    float64
dtype: object

DataFrame.to_numpy() は、単一のdtypeのNumPy配列を返し、 その型はいわば「最小公倍数」、すなわち、元のDataFrameの全ての型を格納できるdtypeとなる。 これによりアップキャストが発生する場合がある。

In [348]: df3.to_numpy().dtype
Out[348]: dtype('float64')

astype

明示的に1つのdtypeから他のdtypeに変換するには、astypeメソッドを使うことができる。 astypeメソッドは*1デフォルトではコピーを返す。 これはdtypeが変わらない場合でも同じである。(動作を変更するには、copy=Falseパラメータを渡す。) さらに、astype操作が無効である場合、astypeメソッドは例外を発生させる。

アップキャストは常にNumPyの規則に従う。 ある演算に2つの異なるdtypeが関与している場合は、演算の結果には より汎用的なほうの型が使われる。

In [349]: df3
Out[349]: 
          A         B      C
0  1.047606  0.256090    0.0
1  3.497968  1.426469    0.0
2 -0.150862 -0.416203  255.0
3  0.724370  1.139976    0.0
4 -1.203098 -1.193477    0.0
5  1.346426  0.096706    0.0
6 -0.052599 -1.956850    1.0
7 -0.756495 -0.714337    0.0

In [350]: df3.dtypes
Out[350]: 
A    float32
B    float64
C    float64
dtype: object

# dtypesを変換する
In [351]: df3.astype('float32').dtypes
Out[351]: 
A    float32
B    float32
C    float32
dtype: object

astypeを使って、一部の列を特定の型に変更する。

In [352]: dft = pd.DataFrame({'a': [1, 2, 3], 'b': [4, 5, 6], 'c': [7, 8, 9]})

In [353]: dft[['a', 'b']] = dft[['a', 'b']].astype(np.uint8)

In [354]: dft
Out[354]: 
   a  b  c
0  1  4  7
1  2  5  8
2  3  6  9

In [355]: dft.dtypes
Out[355]: 
a    uint8
b    uint8
c    int64
dtype: object
New in version 0.19.0.

astype()にdictを渡すことで、ある列(複数でもよい)を特定の型に変換する。

In [356]: dft1 = pd.DataFrame({'a': [1, 0, 1], 'b': [4, 5, 6], 'c': [7, 8, 9]})

In [357]: dft1 = dft1.astype({'a': np.bool, 'c': np.float64})

In [358]: dft1
Out[358]: 
       a  b    c
0   True  4  7.0
1  False  5  8.0
2   True  6  9.0

In [359]: dft1.dtypes
Out[359]: 
a       bool
b      int64
c    float64
dtype: object

注意: astype()およびloc()を用いて、列の部分集合を指定した型へと変換しようとした場合、アップキャストが発生する。 []はdtypeを括弧内で指定された型に変換するが*2、 その一方でloc()はオブジェクトを現在のdtypeに戻して代入しようとする。 ゆえに、下のコードの結果は意図した通りにならない。

In [360]: dft = pd.DataFrame({'a': [1, 2, 3], 'b': [4, 5, 6], 'c': [7, 8, 9]})

In [361]: dft.loc[:, ['a', 'b']].astype(np.uint8).dtypes
Out[361]: 
a    uint8
b    uint8
dtype: object

In [362]: dft.loc[:, ['a', 'b']] = dft.loc[:, ['a', 'b']].astype(np.uint8)

In [363]: dft.dtypes
Out[363]: 
a    int64
b    int64
c    int64
dtype: object

オブジェクトの変換

pandasには、object dtypeから他の型に変換するための様々な関数が用意されている。 データが既に正しい型であるが、object型の配列に格納されている場合、 DataFrame.infer_objects() と Series.infer_objects()メソッドは正しい型にソフトに変換するのに使うことができる。(訳注:soft convertはこれ以上の説明が無い。「ユーザが型を指定せずに、データの中身から型を類推して変換してもらうこと」を指すか?)

In [364]: import datetime

In [365]: df = pd.DataFrame([[1, 2],
   .....:                    ['a', 'b'],
   .....:                    [datetime.datetime(2016, 3, 2),
   .....:                     datetime.datetime(2016, 3, 2)]])
   .....: 

In [366]: df = df.T

In [367]: df
Out[367]: 
   0  1          2
0  1  a 2016-03-02
1  2  b 2016-03-02

In [368]: df.dtypes
Out[368]: 
0            object
1            object
2    datetime64[ns]
dtype: object

データを転置したので、元々の型推測によって全ての列がobject型として格納されているが、infer_objectsを使うと修正される。 (訳注:「全ての列」と言いつつdatetime64[ns]型の列がある。謎。)

In [369]: df.infer_objects().dtypes
Out[369]: 
0             int64
1            object
2    datetime64[ns]
dtype: object

1次元の配列もしくはスカラーに対して、特定の型にハード変換するためには、以下の関数が使用できる。 (訳注:hard convertはこれ以上の説明が無い。「ユーザが『数値型にせよ』『日付型にせよ』などと型を指定して、データを変換すること」を指すか?)

to_numeric() (数値のdtypeへの変換)

In [370]: m = ['1.1', 2, 3]

In [371]: pd.to_numeric(m)
Out[371]: array([1.1, 2. , 3. ])

to_datetime() (datatime objectへの変換)

In [372]: import datetime

In [373]: m = ['2016-07-09', datetime.datetime(2016, 3, 2)]

In [374]: pd.to_datetime(m)
Out[374]: DatetimeIndex(['2016-07-09', '2016-03-02'], dtype='datetime64[ns]', freq=None)

to_timedelta() (timedelta objectへの変換)

In [375]: m = ['5us', pd.Timedelta('1day')]

In [376]: pd.to_timedelta(m)
Out[376]: TimedeltaIndex(['0 days 00:00:00.000005', '1 days 00:00:00'], dtype='timedelta64[ns]', freq=None)

型変換を強制する際には、errors引数を渡すことができる。(訳注:errors引数については「Pythonデータ分析/機械学習のための基本コーディング! pandasライブラリ活用入門」の第7章「データ型の概要と変換」にも同様の記述あり。) これにより、希望するdtypeやobjectに変換できなかった要素について、pandasがどう扱うかを指定する。 デフォルトではerrors='raise'であり、これは変換の過程の中で何らかのエラーに遭遇したらエラーが上がるという意味である。 しかし、もしerrors='coerce'を指定すれば、エラーは無視され、 pandasは問題を引き起こした要素をpd.NaT(datetime と timedeltaの場合)もしくはnp.nan(数値型の場合)に変換する。 この動作が便利かもしれないのは、読み込んだデータのほとんどが所望のdtype(例えば数値型やdatetime)であるが、 時々適合しない要素も混ざっていて、それらの要素を欠損として扱いたい場合である。

In [377]: import datetime

In [378]: m = ['apple', datetime.datetime(2016, 3, 2)]

In [379]: pd.to_datetime(m, errors='coerce')
Out[379]: DatetimeIndex(['NaT', '2016-03-02'], dtype='datetime64[ns]', freq=None)

In [380]: m = ['apple', 2, 3]

In [381]: pd.to_numeric(m, errors='coerce')
Out[381]: array([nan,  2.,  3.])

In [382]: m = ['apple', pd.Timedelta('1day')]

In [383]: pd.to_timedelta(m, errors='coerce')
Out[383]: TimedeltaIndex([NaT, '1 days'], dtype='timedelta64[ns]', freq=None)

errors引数には3番目の選択肢errors='ignore'があり、この場合、所望のデータ型に変換するときに何らかのエラーが発生したら、渡されたデータを変換せずにそのまま返す。 

In [384]: import datetime

In [385]: m = ['apple', datetime.datetime(2016, 3, 2)]

In [386]: pd.to_datetime(m, errors='ignore')
Out[386]: Index(['apple', 2016-03-02 00:00:00], dtype='object')

In [387]: m = ['apple', 2, 3]

In [388]: pd.to_numeric(m, errors='ignore')
Out[388]: array(['apple', 2, 3], dtype=object)

In [389]: m = ['apple', pd.Timedelta('1day')]

In [390]: pd.to_timedelta(m, errors='ignore')
Out[390]: array(['apple', Timedelta('1 days 00:00:00')], dtype=object)

オブジェクトの変換に加えて、to_numeric()にはもう一つの引数downcastがある。 引数downcastを使うと、新しく数値型になった(もしくは既に数値型の)データを、より小さいdtypeにダウンキャストできる。 これによってメモリが節約できる。

In [391]: m = ['1', 2, 3]

In [392]: pd.to_numeric(m, downcast='integer')   # smallest signed int dtype
Out[392]: array([1, 2, 3], dtype=int8)

In [393]: pd.to_numeric(m, downcast='signed')    # same as 'integer'
Out[393]: array([1, 2, 3], dtype=int8)

In [394]: pd.to_numeric(m, downcast='unsigned')  # smallest unsigned int dtype
Out[394]: array([1, 2, 3], dtype=uint8)

In [395]: pd.to_numeric(m, downcast='float')     # smallest float dtype
Out[395]: array([1., 2., 3.], dtype=float32)

上述したメソッドは、1次元の配列、リスト、スカラーにしか使えない。 DataFrameのような多次元のオブジェクトに直接使うことはできない。 しかし、apply()を使えば、効率的に各列に関数を適用(apply)できる。

In [396]: import datetime

In [397]: df = pd.DataFrame([
   .....:     ['2016-07-09', datetime.datetime(2016, 3, 2)]] * 2, dtype='O')
   .....: 

In [398]: df
Out[398]: 
            0                    1
0  2016-07-09  2016-03-02 00:00:00
1  2016-07-09  2016-03-02 00:00:00

In [399]: df.apply(pd.to_datetime)
Out[399]: 
           0          1
0 2016-07-09 2016-03-02
1 2016-07-09 2016-03-02

In [400]: df = pd.DataFrame([['1.1', 2, 3]] * 2, dtype='O')

In [401]: df
Out[401]: 
     0  1  2
0  1.1  2  3
1  1.1  2  3

In [402]: df.apply(pd.to_numeric)
Out[402]: 
     0  1  2
0  1.1  2  3
1  1.1  2  3

In [403]: df = pd.DataFrame([['5us', pd.Timedelta('1day')]] * 2, dtype='O')

In [404]: df
Out[404]: 
     0                1
0  5us  1 days 00:00:00
1  5us  1 days 00:00:00

In [405]: df.apply(pd.to_timedelta)
Out[405]: 
                0      1
0 00:00:00.000005 1 days
1 00:00:00.000005 1 days

落とし穴

(訳注:セクション名について*3

整数型のデータに対して要素の選択を実行すると、データが浮動小数点数にアップキャストされがちである *4。 結果にnanが含まれていない場合には、入力データのdtypeは保存される。 [整数のNAのサポート]も参照。

In [406]: dfi = df3.astype('int32')

In [407]: dfi['E'] = 1

In [408]: dfi
Out[408]: 
   A  B    C  E
0  1  0    0  1
1  3  1    0  1
2  0  0  255  1
3  0  1    0  1
4 -1 -1    0  1
5  1  0    0  1
6  0 -1    1  1
7  0  0    0  1

In [409]: dfi.dtypes
Out[409]: 
A    int32
B    int32
C    int32
E    int64
dtype: object

In [410]: casted = dfi[dfi > 0]

In [411]: casted
Out[411]: 
     A    B      C  E
0  1.0  NaN    NaN  1
1  3.0  1.0    NaN  1
2  NaN  NaN  255.0  1
3  NaN  1.0    NaN  1
4  NaN  NaN    NaN  1
5  1.0  NaN    NaN  1
6  NaN  NaN    1.0  1
7  NaN  NaN    NaN  1

In [412]: casted.dtypes
Out[412]: 
A    float64
B    float64
C    float64
E      int64
dtype: object

一方で、浮動小数点数のdtypeはnanが含まれても変更されない。

In [413]: dfa = df3.copy()

In [414]: dfa['A'] = dfa['A'].astype('float32')

In [415]: dfa.dtypes
Out[415]: 
A    float32
B    float64
C    float64
dtype: object

In [416]: casted = dfa[df2 > 0]

In [417]: casted
Out[417]: 
          A         B      C
0  1.047606  0.256090    NaN
1  3.497968  1.426469    NaN
2       NaN       NaN  255.0
3       NaN  1.139976    NaN
4       NaN       NaN    NaN
5  1.346426  0.096706    NaN
6       NaN       NaN    1.0
7       NaN       NaN    NaN

In [418]: casted.dtypes
Out[418]: 
A    float32
B    float64
C    float64
dtype: object

*1:この訳だと単数のmethodをTheseで受けていることになり変だ。自信がない

*2:ここの訳にぜんぜん自信がない。原文が間違っていないか?

*3:gotchaを「落とし穴」と訳した背景はこちらを参照。 linus-mk.hatenablog.com

*4:訳注:実のところ、このドキュメントを訳そうと思ったきっかけは、データが浮動小数点数に意図せずアップキャストされていたのが原因である。この記事を参照。 linus-mk.hatenablog.com

gotchaが名詞の場合の意味。

まとめ:
(特にコンピュータ・プログラミング関連で)gotchaという単語が名詞として使われた場合は「落とし穴、引っかかりやすい箇所」の意味

きっかけ

pandasドキュメントを読んでいたら、セクションのタイトルにgotchasと書いてあった。 Essential basic functionality — pandas 0.25.3 documentation
gotchaは普通「分かったよー」という意味(間投詞)のはずだが、sが付いているからどう見ても名詞だ。一体どういう意味なんだろうか?

英辞郎

英語の意味を調べるならまずは英辞郎ですよねー、と調べたが……

gotcha
【間投】
1. 〈話〉〔探している人を〕見つけた、捕まえた◆(I've) got you.の発音つづり
2. 〈話〉仕留めた、やっつけた、もう逃げられないぞ◆投げたもの・飛ばしたものなどが相手・獲物などに当たったときなど。
3. 〈話〉分かった、了解◆相手の言ったことの意味が分かったときなど。
4. 〈話〉引っかかった◆相手を驚かすために軽いうそをついて、相手がそれを信じたときなど。
【名】
1. 〈話〉〔探している人を〕見つけること、捕まえること
2. 〈話〉逮捕

流石に「逮捕」ではないだろう。「誰かを見つけること」もおかしい。
というわけで、英辞郎には探している意味が載っていなかった。

Urban Dictionary

俗語・スラングを調べるなら強いのがUrban Dictionary
「色々な人が単語の定義を投稿して、投票により選ばれたものが上位表示される」という仕組みなので、既存の辞書よりは信頼性が落ちることは注意。
とはいえ、通常の辞書に出てこないような新語・俗語などには非常に強い。

Urban Dictionary: gotcha

An annoying or unfavorable feature of a product or item that has not been fully disclosed or is not obvious.
例文:The new camera takes excellent photos, but one major gotcha is its slow processing time.
拙訳:製品・品物の、迷惑なあるいは好ましくない特徴で、完全には公表されていなかったり明白でなかったりするもの。
例文:この新しいカメラは素晴らしい写真が撮れるが、しかし一つの大きな落とし穴は処理速度が遅いことだ。

「欠点・短所」でもいいけど、「隠れた欠点」というような意味のようだ。

A proverbial pitfall. Something that has negative consequences that are easily overlooked.
A common gotcha is forgetting to push in the fuel cutoff.
A common gotcha is to rely on the computer and not check its output.
よく知られた落とし穴。悪い結果をもたらすが、すぐに見過ごされてしまうもの。
例文:よくある落とし穴は、燃料遮断(スイッチ?)を押し忘れてしまうことだ。 例文:よくある落とし穴は、コンピューターに頼ってその出力結果が正しいことを確認しないことだ。 

「pitfall」は「落とし穴」という意味。なのでgotchaも「落とし穴」と訳して良さそうだ。

Wikipedia

Gotcha (programming) - Wikipedia

わざわざ「プログラミング」と断ったうえで記事を立てている。プログラミング独特の

In programming, a gotcha is a valid construct in a system, program or programming language that works as documented but is counter-intuitive and almost invites mistakes because it is both easy to invoke and unexpected or unreasonable in its outcome.
プログラミングにおいて、gotchaは、システム・プログラム・プログラミング言語の正当な概念であって、ドキュメントに書かれたとおりに動くが、しかし直感的ではないため、よく間違いの原因となるものである。なぜなら間違いを引き起こしやすいし、その結果が予期せず理にかなっていないからである。 

訳してはみたけどイマイチ分かりにくいね……
その後にgotchaの例として、C/C++if (a = b) code; が挙がっている。これは確かに言語の文法規則上有効だし、規格通りに動くから、(言語処理系の)バグではない。しかしこれを書く人は、if (a == b) code; と書くつもりでうっかり間違えてこう書いているケースが多い。そのため間違いの原因となる。

つまり……gotchaというのは、バグではなく、仕様通りの動作ではあるが、それが予想した通りではない、という箇所らしい。

由来

gotchaという単語はもともと、I've got youの略(音韻変化)である。
英辞郎にある通り、「見つけた!捕まえた!」あるいは「やーい、(私の嘘に)引っかかったな!」という 今回の主題であった名詞のgotchaは、「ある状況が人を、捕まえて引っかける」というのがおそらく由来だろう。 言い換えれば、「人がある状況にハマる、引っかかる」という意味になる。

今日のまとめ

「バグ(不具合・誤り)」とは違う概念なのは間違いなさそうだ。 しかしその意味は各サイトによって微妙に異なるので、「公表されていないもの」とか「ドキュメントに書いたとおりに動くが」の要素は参考程度に見るのが良いだろう。
全体としては「落とし穴、引っかかりやすい箇所」くらいの意味になる。

参考:
meaning - What does 'gotcha' mean? - English Language & Usage Stack Exchange 単に「問題が起きそうな箇所」くらいの意味でも使う?
コンピュータ業界でよく出る英語 - Qiita 最初にgotchaの説明あり

エンジニアの心を整える技術 感想

技術書典7で買った「エンジニアの心を整える技術 誰でも実践できる心のリファクタリング術」を読み終えたので、感想を書く。

どんな本?

「エンジニアの心を整える技術」をnoteで2章まで無料公開します! #感想まとめ #技術書典6|karamage@柿本 匡章|note

感想

1章が「はじめに」に相当している。その中で 「あなたが本書を読むことによって、マインドフルネスとアドラー心理学について学び、心がリファクタリングされ、日々の仕事が充実したことになることを願っています(p.12)」って書いてあるから、「そうか、この本は」 そしたら2章と3章が関係ない話で、え?何で?ってなった。しかもそれが結構ページ数が多いから困る。 4章が「マインドフルネス」、5章が「アドラー心理学」だけど、その本題に入るまでに長い前置きがある。

思わず読みながらツイートしてしまったけど、2~3章に関してはこのツイートのとおりの感想。

5章はアドラー心理学なんだけど、あれって「悩みなんて、気の持ちよう次第やで!」みたいな傾向が強いから、 論理ガチガチの考え方をしている俺としてはやっぱり受け入れづらいのよね……。
「嫌われる勇気」でもきっちり1冊読み通したらその考えも変わるんだろうか。

行動3つ

この間、write-blog-every-weekの「書評記事は『この本を読んで、こういう行動します』を3つ書いておくと良いで」って話が上がったので。

  • 昼飯を食べているときに(なるべく)スマホをいじらない
  • 歩いているときに身体感覚に意識を向ける
  • 自分の課題と相手の課題(自分でコントロールできるものとコントロールできないもの)を区別する

一点目は普段一人で飯食ってるときはほぼ間違いなくスマホ見てたので、この本を読んでからなるべく控えています。 情報入手大好きなので、無限にスマホを見ていられるからな……気を付けないと。 ただし100%を目指すとそれはそれで疲れてしまうので、比較的テキトーな昼飯の場合は(松屋とか)、スマホを弄ってTwitter見てることもある。

二点目。これも歩くときは歩くことに集中しましょうってことで、一点目と同根だろうな。

三点目は「自分の力でどうにもならないことについて思い悩まないようにする」って感じですね。