メモ:Matplotlibのグラフの書き方が2通りある話

概要

Matplotlibのインターフェースには2通りある。MATLABに近い書き方と、オブジェクト指向のインターフェースである。公式は後者を推奨している。

私も昨日知ったばかりでまだまだ詳しくない。メモ程度に書いておくので、詳細は参考文献などを見てほしい。

参考文献

まずは公式ドキュメントを示したほうが良いだろう。
https://matplotlib.org/tutorials/introductory/lifecycle.html#a-note-on-the-object-oriented-api-vs-pyplot

Pythonデータサイエンスハンドブック」の「4.2 同じ結果を得る2つのインターフェース」に載っている。
この本は英語版が無料で公開されているおり、該当箇所は以下である。
Visualization with Matplotlib | Python Data Science Handbook

Pythonによるデータ分析入門 第2版」にも「9.1.3 目盛り、ラベル、凡例」のところに記述がある。ただし1ページ足らずしかないので、「Pythonデータサイエンスハンドブック」のほうを勧める。

ネット上で読める日本語のだと、以下の2つが良さそう。
早く知っておきたかったmatplotlibの基礎知識、あるいは見た目の調整が捗るArtistの話 - Qiita
matplotlib入門 - りんごがでている の「オブジェクト指向なプロット」部分

呼び方の対応表

公式に定まった呼び方が無いようので、本や記事によって微妙に違う言葉で表現されてたりする。

オブジェクト指向インターフェース MATLABスタイルインターフェース
pyplotインターフェース(出典)
OOP-style(出典、p.252) MATLAB-style(出典、p.252)
explicit interface(出典 implicit interface(出典

以下で2つのインターフェースのそれぞれについて書く。

MATLABスタイルインターフェース

pyplotを用いたインターフェースで、状態ベースである。MATLABに近い書き方。
端的に言うと、import matplotlib.pyplot as pltをしたあとでplt.xxxによってグラフを描いていくやり方のこと。

重要なのは、plt.xxxは「今描いている図」に対する操作になっていることである。これは明示的に指定しなくても良いので、簡単な図をサッと作りたいときには便利。

plt.gcf() (get current figure)とplt.gca() (get current axes)を使うと、現在のFigureオブジェクトやAxesオブジェクトを取得できる。

オブジェクト指向インターフェース

FigureオブジェクトやAxesオブジェクトに対するメソッドを使う方法のこと。
端的に言うと、fix, ax = plt.subplots()とした上でax.xxxによってグラフを描いていくやり方のこと。

どっちが良いの

Matplotlib公式より

Note
In general, try to use the object-oriented interface over the pyplot interface.

https://matplotlib.org/tutorials/introductory/lifecycle.html#a-note-on-the-object-oriented-api-vs-pyplot

公式の別のページからもう1箇所。

Note
the pyplot API is generally less-flexible than the object-oriented API. Most of the function calls you see here can also be called as methods from an Axes object. We recommend browsing the tutorials and examples to see how this works.

https://matplotlib.org/tutorials/introductory/pyplot.html

Matplotlib公式は、一般的にオブジェクト指向インターフェースの方が良いとしている。

Pythonデータサイエンスハンドブック」より

こうした状態を持つインターフェース(注:MATLABスタイルのこと)は、簡単なプロットでは高速かつ便利ですが、問題に陥りやすいものでもあります。
(前掲書、225ページ)

より単純なプロットでは、使用するスタイルの選択は単に好みの問題ですが、プロットが複雑になるにつれてオブジェクト指向のアプローチが必要になります。
(前掲書、226ページ)

簡単なグラフを書く際にはMATLABスタイルでもよいが、いろいろと見た目を調整しようとするとオブジェクト指向のスタイルのほうが良いみたい。 それでは。

pandasのSettingWithCopyWarningを理解する (1/3)

この記事は、 SettingwithCopyWarning: How to Fix This Warning in Pandas – Dataquest の日本語訳です。3回にわたって掲載予定で、この記事は1回目です。

f:id:soratokimitonoaidani:20190201234227p:plain

前書き

pandasを書いていたら、SettingWithCopyWarningという警告が出てきた。
うーん。やりたい動作はできてるように見えるし、何がまずいんだかいまいち分からん。

と思って、いつものように警告の名前でググってみると、以下の記事(英語)を発見した。
SettingwithCopyWarning: How to Fix This Warning in Pandas – Dataquest

これはすごい。
この警告についてめっちゃ詳しく書いてある。後半ではpandasの歴史まで立ち入って解説している。
その充実した内容に感嘆すると同時にびっくりした。

日本語では詳しい解説をしている記事が見つからなかった。
SettingWithCopyWarningについて俺が適当な記事を書くよりも、この記事をちゃんと訳したほうがよい。
どう考えてもそのほうが良い。俺のためにもなるし、読む人のためにもなるでしょ!
……と思って翻訳の許可を申し出たところ、快諾をいただいたので、翻訳した。
Google翻訳をベースにしたが、そこから全面的に修正している。この記事を含めて3つに分ける予定である。翻訳が完了し次第、順次公開する予定である。

それでは本文へどうぞ。

本編 はじめに

SettingWithCopyWarningは、pandasを学ぶときに遭遇する最も一般的な困難の1つである。ザッとウェブ検索してみると、Stack Overflowの質問、GitHubのissue、フォーラムの投稿が山のように見つかるだろう。この警告が自分たちの特定の状況で何を意味するのかについて、何とか理解しようとしているプログラマーからのものだ。 多くの人がこれに苦労しているのは当然のことだ。pandasのデータ構造に対するインデックス操作の方法は非常にたくさんあり、それぞれが固有のニュアンスを持っている。pandas自体さえ、同一に見えるかもしれない2行のコードが、同じ結果になることを保証していないのだ。

このガイドでは、警告が生まれる理由とそれを解決する方法について説明する。また、何が起こっているのかをより理解してもらうための内部的な詳細な説明も行う。そして、この話題に関するいくつかの歴史的な経緯も述べていくので、なぜこのように動作するのかについて視点が得られるだろう。

SettingWithCopyWarningを調査するために、以降ではデータセットを使って説明する。eBay上で3日間のオークションでXboxが販売されたときの価格のデータセットであり、書籍 『モデリングオンラインオークション』から引用した。 それでは見てみよう:

import pandas as pd

data = pd.read_csv('xbox-3-day-auctions.csv')
data.head()
- auctionid bid bidtime bidder bidderrate openbid price
0 8213034705 95.0 2.927373 jake7870 0 95.0 117.5
1 8213034705 115.0 2.943484 davidbresler2 1 95.0 117.5
2 8213034705 100.0 2.951285 gladimacowgirl 58 95.0 117.5
3 8213034705 117.5 2.998947 daysrus 10 95.0 117.5
4 8213060420 2.0 0.065266 donnie4814 5 1.0 120.0

ご覧のとおり、このデータセットの各行は、ある特定のeBay Xboxオークションへの1回の入札に関するものだ。以下は各列の簡単な説明である。

  • auctionid - 各オークションの一意の識別子。
  • bid - 入札の金額。
  • bidtime - オークションの中で入札した時間(日単位)。
  • bidder - 入札者のeBayユーザー名。
  • bidderrate - 入札者のeBayユーザー評価。
  • openbid - オークションのために売り手が設定した始値
  • price - オークション終了時の落札価格。

SettingWithCopyWarningとは何か?

最初に理解しておくべきことは、SettingWithCopyWarningは警告であり、エラーではないということだ。

エラーは、何かが壊れていることを示している。例えば無効な構文や、未定義の変数を参照しようとしていることなどだ。一方で、警告の役目は、プログラマの注意を喚起して、コードの中にある潜在的なバグや問題に気づかせることである。警告は出るが、このコードは言語仕様の中で許可されている操作である。この場合、警告は重大ではあるが目立たない間違いを暗示している可能性が非常に高い。

SettingWithCopyWarningが伝えていることは、操作が期待どおりに機能しなかった可能性があること、そして、結果を確認して間違いがないことを確かめる必要があることである。

警告が出ていてもコードが期待した通りに動いている場合は、警告を無視したくなるかもしれない。 これは悪い習慣であり、SettingWithCopyWarningを決して無視してはいけない。 対策を講じる前に、しばらく時間をかけてなぜ警告が発生しているのかを理解しよう。

SettingWithCopyWarningがどういうものかを理解するために、分かっていると役に立つことがある。それは、pandasのいくつかの動作がデータのビューを返す可能性があること、そして他の動作がコピーを返すということだ。

f:id:soratokimitonoaidani:20190201234244p:plain

上の図からわかるように、左側のビューdf2は元のdf1のサブセットにすぎないが、右側のコピーは新しい単一のオブジェクトdf2を作成している。

値を変更しようとすると、両者の違いのせいで問題が起きる恐れがある。

f:id:soratokimitonoaidani:20190201234253p:plain

私たちがしていることによるが、元のdf1を修正したい(左側)かもしれないし、df2だけを修正したい(右側)かもしれない。 警告が知らせていることは、私たちがどちらか一方を実行してほしかったのに、コードがもう片方を実行した可能性があるということだ。

これについては後で詳しく説明するが、ここでは、警告の2つの主な原因とその解決方法について説明をする。

連鎖代入

pandasは連鎖代入(chained assignment)と呼ばれるものを検出すると警告を発生する。説明のために使用する用語をいくつか定義しよう。

  • 代入(Assignment) - あるものの値を設定する操作。例えばdata = pd.read_csv('xbox-3-day-auctions.csv') 。 しばしばセット(set)と呼ばれる。
  • アクセス(Access) - あるものの値を返す操作。例えば、以下のインデックス作成と連鎖の例のような操作。 しばしばゲット(get)と呼ばれる。
  • インデックス(Indexing) - データのサブセットを参照する代入またはアクセスのメソッド。 例えばdata[1:5]。
  • 連鎖 - 複数のインデックス操作を連続して使用すること。 例えばdata[1:5][1:3]。

連鎖代入は、連鎖と代入の組み合わせである。先ほどロードしたデータセットを使った例を簡単に見てみよう。後ほど、もっと詳しくこの件を調査していく。説明の都合上、ユーザー'parakeet2004'の入札者の評価が間違っていると言われたため、更新する必要がある、と仮定しよう。はじめに、現在の値を見てみよう。

data[data.bidder == 'parakeet2004']
- auctionid bid bidtime bidder bidderrate openbid price
6 8213060420 3.00 0.186539 parakeet2004 5 1.0 120.0
7 8213060420 10.00 0.186690 parakeet2004 5 1.0 120.0
8 8213060420 24.99 0.187049 parakeet2004 5 1.0 120.0

bidderrateのフィールドを更新する必要があるのは3行である。では次に、実際に更新してみよう。

data[data.bidder == 'parakeet2004']['bidderrate'] = 100
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/ipykernel/__main__.py:1: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  if __name__ == '__main__':

なんてこった。不思議なことに、SettingWithCopyWarningに出くわしてしまった!

調べてみると、今回の場合、値は変更されていないことが分かる。

data[data.bidder == 'parakeet2004']
- auctionid bid bidtime bidder bidderrate openbid price
6 8213060420 3.00 0.186539 parakeet2004 5 1.0 120.0
7 8213060420 10.00 0.186690 parakeet2004 5 1.0 120.0
8 8213060420 24.99 0.187049 parakeet2004 5 1.0 120.0

警告が発生したのは、2つのindexing操作を連鎖させたためだ。今回は角括弧を2回使用したので、容易に見つけられるが、.bidderrate 、 .loc、 .iloc、.ix[]などの他のアクセス方法を使用した場合も同様である。 連鎖操作は

  • data[data.bidder == 'parakeet2004']
  • ['bidderrate'] = 100

だ。これら2つの連鎖操作は、順番に独立して実行される。1つ目はアクセスメソッド(get操作)である。これは、bidderが'parakeet2004'と等しいすべての行を含むDataFrameを返す。2番目は代入操作(セット操作)であり、この新しいDataFrameに対して呼び出されている。決して、元のDataFrameを操作しているのではない。

解決策は簡単だ。すなわち、元のDataFrameがsetされたことをpandasが保証できるように、 locを使用して連鎖操作をまとめて、単一の操作にする。以下のような連鎖のないset操作がうまく動作することを、pandasは常に保証している。

# Setting the new value
data.loc[data.bidder == 'parakeet2004', 'bidderrate'] = 100

# Taking a look at the result
data[data.bidder == 'parakeet2004']['bidderrate']
6    100
7    100
8    100
Name: bidderrate, dtype: int64

私たちがこのように対処することを警告は提案している。そして今回の場合、それは完璧に上手くいく。

隠れた連鎖

次に、SettingWithCopyWarningに遭遇する2番目に一般的な方法について説明する。落札できた入札を調査しよう。それらを処理するための新しいdataframeを作成する。連鎖代入についての教訓を学んだので、今後はlocを使用するように注意する。

winners = data.loc[data.bid == data.price]
winners.head()
- auctionid bid bidtime bidder bidderrate openbid price
3 8213034705 117.5 2.998947 daysrus 10 95.00 117.5
25 8213060420 120.0 2.999722 djnoeproductions 17 1.00 120.0
44 8213067838 132.5 2.996632 *champaignbubbles* 202 29.99 132.5
45 8213067838 132.5 2.997789 *champaignbubbles* 202 29.99 132.5
66 8213073509 114.5 2.999236 rr6kids 4 1.00 114.5

続けて、winnersという変数を処理するコードを何行か書くかもしれない。

mean_win_time = winners.bidtime.mean()
... # 20 lines of code
mode_open_bid = winners.openbid.mode()

偶然にも、 DataFrameに別の間違いがあるのを発見した。今度は、304というラベルの行のbidderの値が欠損している。

winners.loc[304, 'bidder']
nan

説明の都合上、この入札者の本当のユーザー名を知っていて、データを更新したと仮定しよう。

winners.loc[304, 'bidder'] = 'therealname'
/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/pandas/core/indexing.py:517: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  self.obj[item] = s

またもやSettingWithCopyWarningだ! しかし、私たちはlocを使ったのだ、それなのにどうしてまたこの警告が起きたのだろうか? 調べるために、コードの結果を見てみよう。

print(winners.loc[304, 'bidder'])
therealname

今回はうまくいっている。それでは、なぜ警告が表示されたのだろうか。

連鎖インデックスは、1行の中だけでなく2行にわたって発生する可能性がある。 winnersはget操作( data.loc[data.bid == data.price] )の出力として作成されたため、元のDataFrameのコピーかもしれないし違うかもしれないが、確認してみるまでどちらか知るすべが無かったのだ!私たちがwinnersのインデックス操作をするとき、実際には連鎖インデックスを使っていた。

つまり、 winnersを変更しようとしたときに、dataも同時に変更されている可能性があるということだ。

実際のソフトウェア開発をするときのコードでは、遠く離れた2つの行によって連鎖インデックスが起きる可能性があるため、問題の原因を突き止めることはより難しいかもしれないが、状況は同じことである。

この場合に警告を防ぐための解決策は、新しいdataframeを作成するときに、コピーを作成するようpandasに明示的に指示することである。

winners = data.loc[data.bid == data.price].copy()
winners.loc[304, 'bidder'] = 'therealname'
print(winners.loc[304, 'bidder'])
print(data.loc[304, 'bidder'])
therealname
nan

これだけのことだ!簡単な話である。

秘訣は、連鎖インデックスを特定し、何としてもそれを回避することだ。元々のデータを変更したい場合は、単一の代入操作を使うこと。コピーが欲しいなら、pandasにコピーを作るように命令するのを忘れないこと。これによって時間は節約できるし、あなたのコードは堅牢になる。

また、 SettingWithCopyWarningはsetをしているときに限り発生するが、 getに対しても連鎖インデックスは避けることを勧める。 連鎖操作は速度が遅いし、後から代入操作を追加することにした場合は問題が発生するだろう。


これで元々の記事全体の3分の1くらいだな……この後の部分も頑張って翻訳中だ。
ご意見・ご感想・励ましのお便りをお待ちしています。


2019年5月18日 追記:

2回目 linus-mk.hatenablog.com

3回目 linus-mk.hatenablog.com

2019年の目標を100項目挙げてみた

今年の目標は「何となく」により、100項目挙げてみた。
理由といえば、2018年にやりたいこと100の成果 - kondoyukoのカルチュラル・ハッカーズを見たため、なのかもしれない。

1年の100項目でという形式で目標を立てている人をそこそこ見かける。
中でも、資産運用アドバイザーの内藤忍が言っているのが有名かな。 *1
2015年初:作るだけでも価値がある「今年やるべき100のリスト」
2016年初:お正月のうちに「今年やるべき100のリスト」を作ってしまおう
100項目を達成しなきゃいけないというのではなく、1年の方向性を決めるきっかけとして書き出してみるという目的意識である。

今までやろうと思っていても、始めるきっかけがなかったり、出来ると思っていなかったことが、リストに書くことがきっかけで、1つでも実現できたとしたら、それだけで作ることに意味があります。 お正月のうちに「今年やるべき100のリスト」を作ってしまおう

まずは大きな項目に対して、去年の目標を軽く振り返りながら今年について考える。
去年の目標はこれ(旧ブログ)。
2018年の目標 子供の落書き帳 Remix
最後に今年の目標リストを載せる。

仕事とキャリア

去年の始めに書いてましたね、「キャリアに目処をつけて機械学習エンジニアになる」って。
残念。まだそうなってないです。
そして非常にタスクが少ない手空き状態になることが多い2018年であった。ため息。
正直に言って、今のままずっと働いていてもあまり成長は望めないし、
ケリがつくまではこの方針変更が達成できるように一点集中する所存である。

同年代から怒濤のごとく押し寄せてくる結婚報告 & 子供誕生報告に、「わー、もうそんな年頃か……」と痛切な気持ちになるのも確かだ。
しかしながら、労働環境の方にケリをつけないとプライベートの方には取り掛かれないなと思っている。

競技プログラミング

意外なことに、競技プログラミングに関しては2018年に失速した。
2018年にコンテストに出た回数は5回だけ。最後に出たのが6月だから、現時点で半年以上ブランクがあることになる。
機械学習関係の勉強が優先かなぁと考えているうちに、競技プログラミングの優先順位が下がったんだろうな……

家のこと

詳しい事情は割愛するが、家の中に不要なものがたくさんある。俺が捨てなきゃいけない。
ついでに俺の部屋もやたらとホコリだらけである。
これでは新しい電子機器を買ったところですぐにホコリまみれになってしまう。アカン。どうすりゃええんやろ。
要らないものは捨てて、部屋を整理して、住みやすい環境を作りたい。
……という背景で書いた項目が、下の100項目の中に結構ある。

ブログ

去年の振り返りは下記エントリを参照。

linus-mk.hatenablog.com

今年も週1更新をがんばります。
あと、目的の一つは見た人が「お、こいつは機械学習をやってるやんけ」と思ってくれることなので、それ関係の記事を増やしていくようにしたい。
また、技術一辺倒じゃなくて、読んだ本の感想とかも書いていきたい。
相反するようだけど、どっちも頑張るってことですな。

音ゲー

SDVXの2018年目標の達成状況は次の通り。

  • ○18のクリア1曲以上
  • ○金枠後光魔騎士を取る
  • ×その後で、金枠剛力羅を取る

剛力羅は銀枠(最新回に合格)まで行ったけど、全コース埋める気力と実力がなかった。
今年はもう1つ上のランク、或帝滅斗の取得が主目標である。

しかし書き出してみて思ったけど、音ゲー関連は恐ろしく目標項目が立てやすいな。
難易度が細かく分かれているし、具体的な数値が示せるので、簡単に考えつく……
上記の他の分野もそうならないもんかしら……

2019年の目標100項目(のうち85項目)

書き出したら102個になった。85個を掲載し、86個目以降は非公開とする。

  1. ブログを週に1回書く
  2. 「技術系の記事で」はてブ100以上取る
  3. アマゾンから初めてのアフィリエイトをもらう(まだこれまでの累計が最低単位以下なので)
  4. google analyticsの使い方を把握する
  5. 英語の記事を翻訳したブログ記事を書く
  6. ブログのネタを思いついたらネタ帳に書くことをを習慣化する
  7. 書評記事を5本書く。技術書以外ね
  8. 他の人のリポジトリにコミットする(ソースコード以外)
  9. 他の人のリポジトリにコミットする(ソースコード
  10. 世の中のパズルやゲームに関するプログラムを何かしら作る
  11. youtubeの動画の英訳か和訳を3件以上(公開まで持っていく)
  12. 競技プログラミングのオンサイト大会に出場する(直近の日経で500人以内も含む。これなら行けるかな……?)
  13. 後方逆伝播を一度スクラッチで書く
  14. cycleGANを書く
  15. kaggleでExpert取る
  16. 音ゲーに関する機械学習 or データ分析の趣味プロジェクトをやる
  17. AWSの講座を完走する
  18. 生活のログを取る
  19. Ansibleによる自動化を完成させる
  20. 何かwebサービス作る(適当……)
  21. 個人用のslackbotを作る(いくつかの中から無作為にメッセージを投稿するやつが有力)
  22. LTを6回以上する
  23. (LTではない)長い登壇をする(※輪読会のテキストの解説は含めない)
  24. 開発合宿にいく
  25. 技術系イベントの運営側を手伝う
  26. todoistをでtodo管理をする習慣をつける
  27. 機械学習エンジニアになる
  28. 転職する
  29. 転職エントリを書く
  30. 有給休暇を1ヶ月消化する
  31. その隙にどっか海外に行く(詳細はマジで未定)
  32. 買い物:空気清浄機
  33. 買い物:iPad Pro
  34. 買い物:MacBook Pro
  35. 買い物:電動の歯ブラシ
  36. 歯医者の検診に行く
  37. 買い物:電動のひげそり
  38. 買い物:Nintendo Switch
  39. 買い物:スマブラX
  40. 買い物:キーファーノイのカバン
  41. 革のカバンの手入れをする
  42. 金の適切な使い方を覚える(=買い物を面倒臭がるのをやめる)
  43. 簡単なイラストが描けるようになる
  44. ベッドを廃棄する
  45. 洋服ダンスを廃棄する
  46. 家具以外の物品を50kg以上廃棄する
  47. 書類の廃棄を業者に依頼して実行する
  48. 自室のカーテンを買い替える
  49. 自室の窓の周りを掃除する
  50. 風呂の水道を修理する
  51. 自室のエアコンを修理する
  52. SDVX 金枠剛力羅を取る
  53. SDVX 或帝滅斗を取る
  54. SDVX ありふれたせかいせいふくEXHをフルコンする
  55. SDVX 18を100曲クリアする
  56. SDVX 18を半分クリアする
  57. SDVX 17を100曲ハードクリアする
  58. SDVX 17を全クリする
  59. SDVX 16を1曲フルコンする
  60. DDR ありふれたせかいせいふくEXPERT フルコンする (DDRのほうの目標は無理やり感があるな……)
  61. DDR 激譜面のPFCを+1個
  62. DDR 15全クリア状態を復活?させる
  63. スクフェス 12を1曲フルコンする
  64. 5回以上泊りがけの旅行に行く
  65. これまで行ったこと無い都道府県に行く
  66. 社会人になってからの経県値をまとめる
  67. スキーのレッスンに行く
  68. 撮影旅行いく
  69. 走り寄る犬の写真を撮る
  70. 全日本トレイルの表彰台に乗る
  71. トレイル大会の運営する
  72. 美味しい寿司を食べる
  73. 日本酒の店の新規開拓を6件以上
  74. 今年もマジカルミライに行く
  75. 中島みゆきの夜会に行く
  76. ボイストレーニングいく
  77. 美術館以外の芸術鑑賞にいく
  78. ボヘミアン・ラプソディ観る
  79. 経済学の勉強をする
  80. 出身の高校に寄付する
  81. 怖がらず駐車できるようになる
  82. ろうきんの口座を解約する
  83. じぶん実験(島宗理の本に書いてあったもの)をやってみる
  84. 魔法先生ネギまを最後まで通して読む
  85. 年末に振り返って楽しかったと思える1年にする

それでは。

*1:本業の資産運用に関して、内藤氏のここ数年の主張は変な風になってしまったので、賛成しかねることを注記しておく

古いバージョンのAnsibleでloopを使ったらエラーが出た話

エラーの内容と原因

Ansible では繰り返し制御を使うときにloopキーワードを用いる。
Loops — Ansible Documentation
公式のドキュメントを参考に書いていたが、実行すると以下のエラーになった。

[root@localhost ansible_test]# ansible-playbook my_playbook.yml
ERROR! The field 'loop' is supposed to be a string type, however the incoming data structure is a <class 'ansible.parsing.yaml.objects.AnsibleSequence'>

The error appears to have been in '/root/***/***/my_playbook.yml': line 29, column 7, but may
be elsewhere in the file depending on the exact syntax problem.

The offending line appears to be:

        state: directory
    - name: make directories.
      ^ here

検索するとStackOverflowが出てくる。Ansibleのバージョンが古いのが原因だと書いてある。 いや、まさかそんなはずは……

[root@localhost ansible_test]# ansible --version
ansible 2.3.1.0
(後略)

あっ! そのまさかであった。
loopというキーワードが導入されたのはAnsible 2.5以降である。それより古いバージョンだとloopが正しく認識されず、エラーになる。
したがって、Ansibleのバージョンを最新にすれば問題は解決する。

Ansibleバージョンアップ

ところが、最新にするのが予想に反して大変だった。(環境はCent OS 7.3)

yum update ansibleを打つと、以下のようになった。

依存性を解決しました

=======================================================================================================================================
 Package                                 アーキテクチャー          バージョン                          リポジトリー               容量
=======================================================================================================================================
更新します:
 ansible                                 noarch                    2.4.2.0-2.el7                       extras                    7.6 M
依存性関連でのインストールをします:
 python-cffi                             x86_64                    1.6.0-5.el7                         base                      218 k
 python-enum34                           noarch                    1.0.4-1.el7                         base                       52 k
 python-idna                             noarch                    2.4-1.el7                           base                       94 k
 python-ipaddress                        noarch                    1.0.16-2.el7                        base                       34 k
 python-passlib                          noarch                    1.6.5-2.el7                         extras                    488 k
 python-ply                              noarch                    3.4-11.el7                          base                      123 k
 python-pycparser                        noarch                    2.14-1.el7                          base                      104 k
 python2-cryptography                    x86_64                    1.7.2-2.el7                         base                      502 k
 python2-jmespath                        noarch                    0.9.0-3.el7                         extras                     39 k
依存性関連での更新をします:
 openssl                                 x86_64                    1:1.0.2k-16.el7                     base                      493 k
 openssl-devel                           x86_64                    1:1.0.2k-16.el7                     base                      1.5 M
 openssl-libs                            x86_64                    1:1.0.2k-16.el7                     base                      1.2 M
=======================================================================================================================================

Ansibleのバージョンが2.4.2.0となっている。これではloopが使えない。
最新バージョンは2.7のはずなのに、何でなの……?
繰り返しupdateをしても、「現在のバージョンは最新です」と言われてしまい、2.4.2.0で止まってしまった。

[Ansible]ノートPC Hyper-V 上のCentOS7.5にAnsibleインストール - こしぞーのひとり情シス を参考に

yum install epel-release
yum update ansible

を打ってみたけど、何も変わらない。最新のversionだよって言われる。

https://centos.pkgs.org/7/epel-x86_64/ansible-2.7.5-1.el7.noarch.rpm.html
がある以上、epelが有効ならばVersion 2.7.5.1が使えそうに見えるんだけど。

/etc/yum.repos.d/epel.repo より一部抜粋

[epel]
name=Extra Packages for Enterprise Linux 7 - $basearch
#baseurl=http://download.fedoraproject.org/pub/epel/7/$basearch
metalink=https://mirrors.fedoraproject.org/metalink?repo=epel-7&arch=$basearch
failovermethod=priority
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-7

うーん。enabled=1だからepelが有効になってる設定だよね。

"--enablerepo=epel"を付けて、有効にする指定を明示的に行っても、何も変わらず。

# yum --enablerepo=epel update ansible
読み込んだプラグイン:fastestmirror, langpacks
Loading mirror speeds from cached hostfile
 * base: ftp-srv2.kddilabs.jp
 * epel: epel.scopesky.iq
 * extras: centos.usonyx.net
 * updates: download.nus.edu.sg
No packages marked for update

埒が明かないので、直接rpmを指定してダウンロードし、バージョンアップした。

下記でなんで山形大学ドメインになっているのか、後から見返してよく分からない。
/etc/yum.repos.d/epel.repoの中に書いてある http://download.fedoraproject.org/pub/epel/7/ をブラウザに入力すると
日本国内のミラーに適宜リダイレクトされる。きっと、この時はyamagata-u.ac.jpにリダイレクトされたんだろう。
いま何回か試したらjaist.ac.jp や riken.jp に飛んでいった時もあった。
そこからパッケージを選択して、rpmファイルのあるURLを控えておき、rpmコマンドでそのURLを指定すればよい。

[root@localhost ansible_test]# rpm -Uvh http://ftp.yz.yamagata-u.ac.jp/pub/linux/fedora-projects/epel/7/x86_64/Packages/a/ansible-2.7.5-1.el7.noarch.rpm
http://ftp.yz.yamagata-u.ac.jp/pub/linux/fedora-projects/epel/7/x86_64/Packages/a/ansible-2.7.5-1.el7.noarch.rpm を取得中
準備しています...              ################################# [100%]
更新中 / インストール中...
   1:ansible-2.7.5-1.el7              ################################# [ 50%]
整理中 / 削除中...
   2:ansible-2.4.2.0-2.el7            ################################# [100%]
[root@localhost ansible_test]# ansible --version
ansible 2.7.5
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /usr/bin/ansible
  python version = 2.7.5 (default, Nov  6 2016, 00:28:07) [GCC 4.8.5 20150623 (Red Hat 4.8.5-11)]
[root@localhost ansible_test]#

というわけで何とか最新バージョンになった。

以上。それでは。

行動指針のポスターを貼っても変わらない理由をAIDMAフレームワークから考える

ある日の昼休みの会話

ある日の昼休みのこと。
昼飯を一緒に食べていた同期に「行動指針」の話を持ち出してみた。
すると同期がこう言い出した。
「そう言えば、俺のいる席の近くで、行動指針のポスターを会社がめっちゃ貼ってるんだわ」
「ポスター?」
「うん。多いところだと数メートルおきに貼ってあるよ」
会社の行動指針というものが1年以上前に制定された。
ポスターというのは、4箇条からなる行動指針が、大きく箇条書きしてある、シンプルなものだ。その下に「頑張ろう!」とか書いてあったように思う。
「……で、その行動指針のフレーズってのは、皆が言うようになったの?」
「それが誰も言わない。全く言わないんだ」

興味が湧いた俺は、昼飯を済ませたあとに「ちょっと面白そうだから」と言って、別のフロアにある同期の席まで行ってみた。
案内してくれる同僚に付いて回ると、確かに到るところ、数メートルおきに、行動指針のポスターが貼ってある。

色々考えてAIDMAフレームワークの適用を思いついた

最近になって、行動指針に関する目標が新たに制定された。
みんなが日常会話の中で自然と使っている状態というのが目標らしい。
(この目標じたい、行動指針を制定してから1年以上経ってから公開されたので、遅きに失した感がある。目指すべき目標を真っ先に明確にすべきだったと思う)

で、現状は確かに、「自然と使っている状態」に全くなっていない。
偉い人(社長とか事業部長とか)が皆の前で話すときには行動指針の語句が出てくる。
しかし、それ以外のシチュエーションの会話では、一度たりとも聞いたことがない。

そもそも、行動指針を策定したのは1年以上前のことで、その時にポスターは少し貼っていたのだ。
しかし、すでに書いたとおり、全くもってこれらの語句は浸透していない状態だ。
だから浸透させるために、ポスターをもっとたくさん貼りまくった。……というのが今の状況だ。

で、「自然と使っている状態」という目標のための施策が、ポスターを貼りまくること、ねぇ。
ポスターを貼るだけで、皆の会話の中に行動指針のフレーズが出てくるものだろうか? そんな簡単か?
もしもそんな簡単だったら、電車内で本の広告があったら全員がその本を買うんじゃないか?
……と考えて、あることを思い出した。

プロモーション(広告宣伝)を考える上で役に立つフレームワークAIDMAである。
というわけで、行動指針とポスターの話をAIDMAフレームワークから考えてみる。

AIDMA

辞書を調べるとAIDMAの定義は以下の通りだ。

AIDMA は、注目(attention)、関心(interest)、欲望(desire)、記憶(memory)、行動(action)から〕
見込み客が製品を買うにいたるまでの心の動きの過程についての法則。AIDCA の法則における確信を記憶に置き換えたもの。

AIDMAの法則とは - コトバンク より引用

ただし、AIDMAには非常に多くのバリエーションが存在する。(AIDCAとかAIDAとかAISASとか……)
絶対的な法則ではなく、大まかな傾向としてこんなもの、と考えるくらいで良いみたいだ。

プロモーション(広告宣伝)をする側は、顧客がどの段階に属しているかを考えて、それに合致した広告を打つ必要がある。

行動指針を定着させる施策に対する違和感の正体

さて、AIDMAフレームワークを「行動指針」に応用してみよう。
(もちろん、「会社と従業員の関係」と「広告主と購買者の関係」は異なるものである。
会社は従業員に対して権力を持っているので、強権を発動することによって目標を達成するという手段もできる。
例えば、目標とする行動をしなかった人を片っ端から最低の評価にするといった方法を取ることが可能である。
しかし、なぜか分からないが、会社側は強制的に行動指針を押し付けるのではなく、割と広告っぽい方法を取っている。
そのため、「自主的に商品を購入する人」のためのフレームワークをある程度は応用できるのではないか。)

最後の「行動(Action)」に当たるのは、商品を購入することではなく、「行動指針のフレーズを自然と使っている」ことである。
そして、その行動は現状では達成されていない。

で、ポスターをバーンと張り出すのはどの段階の施策だろうか。
部屋の壁のあちらこちらに貼られているので、行動指針の存在を知らないままでいるのが難しいほどである。
つまり「注目(Attention)」のステップは、完了しているだろう。
行動指針のフレーズをただ並べて掲示しただけでは、万人の興味関心を呼び起こすとは考えづらいので、「関心(Interest)」には至らないと思う。

つまり俺の違和感は以下のように言い表せる。
実際に必要なのはその後のI・D・Mあたりに対する施策なのに、
相も変わらず依然として、最初のAの施策を続けているのことが問題なのだ。

購買行動のActionの場合は、「やっぱり値段が高いから」「買ったら場所を取るしなぁ」などの理由で最後の購買行動をためらう場合がありそうだ。
しかしながら、「行動指針のフレーズを自然と使っている」場合はこのような阻害要因は無い。
行動指針の言葉を一回発言するごとに1000円を取られるわけでもないし。
(あるとすれば「発言したら馬鹿にされそう・変な目で見られそう」という心理的な抵抗感だろう。)
したがって、その前のI・D・Mを満たせば、容易にAは達成されると思う。

I「興味関心を持つ」が達成されていない状態の気持ちは
「興味がない・無関心・別にどうでもいい・気に留めない」くらいかな。
これを「お、何か面白そうなことやってるじゃん」に持っていくのはどうやれば良いんだろう?
ちょっとすぐには良い施策が思いつかないや。

freeeの例

最後にちょっとおまけ。
行動指針の話で検索してみると、freeeの事例がよく出てくる。(freeeの場合は「価値基準」と呼んでいる。)

ミーティング中に普通に価値基準が飛び交っててびっくりしました。「それって、マジ価値じゃないよね?」とか「理想ドリブンでいうと、こうだね。」とか。
(中略)
まず、社内アンケートを実施し、現在、価値基準がどのレベルまで浸透しているか把握しました。
(中略)
認知できたとしてもそれを理解して行動に移せないと意味がないですよね。だから既に実施している人にインタビューを実施し、全社的に流して、理解度と行動を促しました。
https://jobs.freee.co.jp/recruitblog/aboutus/kachi-vol1/

行動施策について社員にアンケートを取り、

  • 「認知していない」
  • 「認知しているが理解していない」
  • 「理解しているが行動していない」
  • 「行動している」

の4つに分類した。 上で論じていたAIDMAと同様に、社内の人間を「どこの段階までできているか」で分けている!
その上でそれぞれの層に対する施策を考えて実施している。
こうしてみると、行動指針を浸透し定着させるには、緻密なマーケティングとかプロモーションが必要なんだな……

それでは。

[pandas]特定の条件を満たす行を削除する

f:id:soratokimitonoaidani:20190110230412p:plain
DataFrameから、特定の条件を満たす行を削除する方法について。

例を挙げよう。

import pandas as pd

df = pd.DataFrame({
    'name'    : ['Alice', 'Bob', 'Charlie', 'David', 'Eve', 'Fred'],
    'English' : [12, 34, 56, 78, -1, 90],
    'Math'    : [88, 66, -1, 44, 22, -1]    
})

df
# ->
      name  English  Math
0    Alice       12    88
1      Bob       34    66
2  Charlie       56    -1
3    David       78    44
4      Eve       -1    22
5     Fred       90    -1

上記の通り、生徒と試験の点数が表(DataFrame)になっているとしよう。
試験を欠席した人は点数が-1になっている、としよう。
Mathの得点を集計する前に、Mathの数値が-1である行を除外したい。
どうすればよいだろうか。
NaNを削除するのであればdropna関数が使える。しかし今回の状況では使えない。

正解

df[df['Math'] != -1]

# ->

    name  English  Math
0  Alice       12    88
1    Bob       34    66
3  David       78    44
4    Eve       -1    22

ちょっとした発想の転換が必要である。
「行を削除する」という発想で考えると、なかなか思いつかなかった。
-1の行を削除するので、-1でない行を選択・抽出すれば良い。

df[df.Math != -1]

でも上手くいく。'Math'の列を指定する際の書き方が違うだけだ。

別解

一応、最初に考えた方法もあわせて書いておく。
以下の方法は簡潔ではないことを注意されたい。参考と自分用の整理のために残しておく。
該当行の削除はdropでできるだろう、というところから考え始めると、こうなる。

該当する行を求めるのは以下のコードで可能である。

condition_boolen = (df["Math"] == -1)
condition_boolen

# ->

0    False
1    False
2     True
3    False
4    False
5     True
Name: Math, dtype: bool

しかし、pandas.DataFrame.drop 関数は、True/Falseが並んでいるこの配列(Series)を引数に取れない。

df.drop(condition_boolen)
# ->
エラー(内容は下の折りたたみ部分)

エラー内容(クリックすると展開されます)

---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-7-ba417e4cb86b> in <module>
----> 1 df.drop(condition_boolen)

c:\program files\python37\lib\site-packages\pandas\core\frame.py in drop(self, labels, axis, index, columns, level, inplace, errors)
   3695                                            index=index, columns=columns,
   3696                                            level=level, inplace=inplace,
-> 3697                                            errors=errors)
   3698 
   3699     @rewrite_axis_style_signature('mapper', [('copy', True),

c:\program files\python37\lib\site-packages\pandas\core\generic.py in drop(self, labels, axis, index, columns, level, inplace, errors)
   3109         for axis, labels in axes.items():
   3110             if labels is not None:
-> 3111                 obj = obj._drop_axis(labels, axis, level=level, errors=errors)
   3112 
   3113         if inplace:

c:\program files\python37\lib\site-packages\pandas\core\generic.py in _drop_axis(self, labels, axis, level, errors)
   3141                 new_axis = axis.drop(labels, level=level, errors=errors)
   3142             else:
-> 3143                 new_axis = axis.drop(labels, errors=errors)
   3144             result = self.reindex(**{axis_name: new_axis})
   3145 

c:\program files\python37\lib\site-packages\pandas\core\indexes\base.py in drop(self, labels, errors)
   4402             if errors != 'ignore':
   4403                 raise KeyError(
-> 4404                     '{} not found in axis'.format(labels[mask]))
   4405             indexer = indexer[~mask]
   4406         return self.delete(indexer)

KeyError: '[False False  True False False  True] not found in axis'

ではどうすれば良いかというと、 drop関数の引数には、削除したい列のindexのリストを指定する必要がある。

labels : single label or list-like Index or column labels to drop. https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.drop.html より

rows_to_drop = df.index[df["Math"] == -1]
rows_to_drop

# ->
Int64Index([2, 5], dtype='int64')

df.drop(rows_to_drop)
# ->
    name  English  Math
0  Alice       12    88
1    Bob       34    66
3  David       78    44
4    Eve       -1    22

"Math"が-1の行を選択する操作と、indexを取る操作とは、交換可能である。

df[df["Math"] == -1].index

# ->
Int64Index([2, 5], dtype='int64')

余談だが、インデックス()を df.index は df['index']と書くことはできない。エラーになる。

df['index']
# ->
エラー(内容は下の折りたたみ部分)

エラー内容(クリックすると展開されます)

---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
c:\program files\python37\lib\site-packages\pandas\core\indexes\base.py in get_loc(self, key, method, tolerance)
   3077             try:
-> 3078                 return self._engine.get_loc(key)
   3079             except KeyError:

pandas\_libs\index.pyx in pandas._libs.index.IndexEngine.get_loc()

pandas\_libs\index.pyx in pandas._libs.index.IndexEngine.get_loc()

pandas\_libs\hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()

pandas\_libs\hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()

KeyError: 'index'

During handling of the above exception, another exception occurred:

KeyError                                  Traceback (most recent call last)
<ipython-input-11-1cd2fcbdc823> in <module>
----> 1 df['index']

c:\program files\python37\lib\site-packages\pandas\core\frame.py in __getitem__(self, key)
   2686             return self._getitem_multilevel(key)
   2687         else:
-> 2688             return self._getitem_column(key)
   2689 
   2690     def _getitem_column(self, key):

c:\program files\python37\lib\site-packages\pandas\core\frame.py in _getitem_column(self, key)
   2693         # get column
   2694         if self.columns.is_unique:
-> 2695             return self._get_item_cache(key)
   2696 
   2697         # duplicate columns & possible reduce dimensionality

c:\program files\python37\lib\site-packages\pandas\core\generic.py in _get_item_cache(self, item)
   2487         res = cache.get(item)
   2488         if res is None:
-> 2489             values = self._data.get(item)
   2490             res = self._box_item_values(item, values)
   2491             cache[item] = res

c:\program files\python37\lib\site-packages\pandas\core\internals.py in get(self, item, fastpath)
   4113 
   4114             if not isna(item):
-> 4115                 loc = self.items.get_loc(item)
   4116             else:
   4117                 indexer = np.arange(len(self.items))[isna(self.items)]

c:\program files\python37\lib\site-packages\pandas\core\indexes\base.py in get_loc(self, key, method, tolerance)
   3078                 return self._engine.get_loc(key)
   3079             except KeyError:
-> 3080                 return self._engine.get_loc(self._maybe_cast_indexer(key))
   3081 
   3082         indexer = self.get_indexer([key], method=method, tolerance=tolerance)

pandas\_libs\index.pyx in pandas._libs.index.IndexEngine.get_loc()

pandas\_libs\index.pyx in pandas._libs.index.IndexEngine.get_loc()

pandas\_libs\hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()

pandas\_libs\hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()

KeyError: 'index'

以上。それでは。

技術書の電子書籍(PDF版)を年末年始にセールしているサイトをまとめた

技術書の電子書籍(PDF版)を年末年始にセールしているサイトをまとめた

これは何?

  • 技術書(コンピュータ関連書籍)の
  • Amazon kindleとかhontoのような「プラットフォーム」の上の電子書籍ではない、PDF版を販売してるところで
  • 年末年始にセールを実施しているサイト

をまとめたものである。

2019年1月1日現在の情報を元に書いている。
ただ、電子書籍販売サイトでは毎年同じ時期に同じようなキャンペーンをやると思うので、来年以降の参考にもなるのではないかと思う。
冬休みに読む本を探してみてはどうだろうか。

セールしてるところ

達人出版会

達人出版会は、複数の出版社の電子書籍を発行している会社。紙版は取り扱わず、電子書籍専門の出版社である。
はてなブログ上に公式ブログもある。ただし更新頻度はそれほど高くない。
達人出版会日記

セールお知らせのページはこちら。
プログラミング書フェア - 達人出版会

近代科学社インプレスインプレスR&Dのコンピュータ書の中から選ばれたタイトルを、期間限定で50%OFFにてご提供します!
・セール期間:2018年12月21日~2019年1月10日
・対象タイトル:インプレスインプレスR&D、近代科学社の中から選ばれたタイトル

翔泳社

翔泳社の販売サイトがSEshopだ。 SEshop.com | 翔泳社の通販

セールお知らせのページはこちら。
ポイントUPキャンペーン | SEshop.com | 翔泳社の通販

ゆく年くる年】PDF版電子書籍ポイント3倍キャンペーン
キャンペーン期間中にSEshop内のPDF版電子書籍(一部対象外)をご購入の方には、本体価格の30%をポイント還元いたします。貯まったポイントは1ポイント=1円として次回のお買い物からご利用いただけます。通常10%の還元率が3倍となるお得なチャンスですので、ぜひご利用ください!
【キャンペーン期間】
2018年12月29日(土)~ 2019年1月6日(日)

コンピュータ関連の本はもちろん、ビジネス書などもセール対象商品だ。
翔泳社から届いた昨年(2018年)のメールを見ると、8月や10月にもポイント3倍(30%)セールをやっていた。
結構高い頻度でセールを実施しているようだ。

インプレス

株式会社インプレスの直販ページ、インプレス ブックス。 インプレスブックス - 本、雑誌と関連Webサービス

セールお知らせのページはこちら。
【対象商品50%OFF】インプレスブックス 電子書籍 年末年始セール 2018-2019 - インプレスブックス

【対象商品50%OFF】インプレスブックス 電子書籍 年末年始セール 2018-2019

ただいま、インプレスブックスにて人気の電子書籍1,100タイトル以上が50%OFF「電子書籍 年末年始セール 2018-2019」を開催中です。気になっていたけど買いそびれたあの本やこの本をぜひお求めください。
2018年12月25日(火)~ 2019年1月14日(月)

2017年・2018年とも年末年始にセールを実施しているので、ここは毎年この時期に安売りになるのだろう。
【全品50%OFF以上】インプレスブックス 年末年始&初売りセール 2017 - インプレスブックス
【対象商品50%OFF】インプレスブックス 電子書籍 年末年始セール 2017-2018 - インプレスブックス

Manatee

今日初めて存在を知ったサイト。 Tech Book Zone Manatee

Manateeとは|Tech Book Zone Manatee

「Tech Book Zone Manatee」はマイナビ出版が運営するITを学ぶ方に向けた電子書籍ストアです。
趣旨にご賛同いただいた出版社の技術書の電子書籍販売をはじめ、プログラミングや開発技法、資格関連の連載など、多様なコンテンツをご提供いたします。

Manatee参画出版社
- 株式会社インプレス
- 株式会社シーアンドアール研究所
- 株式会社ビー・エヌ・エヌ新社
- 株式会社ボーンデジタル
- 株式会社マイナビ出版(当サイト運営会社)

マイナビ出版を中心に、他の出版社も扱っているサイトらしい。

セールお知らせのページはこちら。
人気タイトルが20~50%OFF!! 平成最後の年末年始感謝祭|Tech Book Zone Manatee

Tech Book Zone Manateeでは、2018年の感謝を込めて、電子書籍人気タイトルが最大50%OFFとなるキャンペーンを開催します。
開催期間:
2018年12月19日(水)~2019年1月14日(日・祝)

なお、マイナビブックスの方ではセールしてない模様。
マイナビブックス

調べたけどセールしてないところ

オライリー

技術書の出版社として真っ先に思いつくのはオライリーだが、特に安売りのお知らせは無かった。
2018年の始めにはTシャツなどが当たる福袋をプレゼントしていたようだ。(紙版の書籍だけ。電子書籍は対象外)
Sales Information - 【受付を締め切りました】品切れ必至!今年もやります1月恒例プレゼント - 2018年1月のWeb直販キャンペーン

技術評論社

技術評論社電子書籍(電子出版)販売サイトは、Gihyo Digital Publishingである。
Gihyo Digital Publishing … 技術評論社の電子書籍
しかし、お知らせページを見てもセール情報はない。ほとんどセールをしていないと思われる。

日経BP

サイトの構造がよく分からなかった。
具体的な本のページを見てみると、kindleやhontoなどへのリンクが貼ってあって自社サイトへのリンクがない。自社では電子書籍版を取り扱っていないらしい。
HARD THINGS|日経BPブックナビ【公式サイト】
……ほとんど同じページがもう一つあるんだけど、一体どうなってんだ?
日経BP SHOP|HARD THINGS

補足

参考にしたサイト

下記のサイトを参考にして、出版社の電子書籍販売サイトを探した。
技術書・参考書はDRMフリーなPDFが一番!DRMフリーPDFの電子書籍が購入できるサイトまとめ
パソコンで読める技術系電子書籍の販売サイトまとめ

サイト終了の情報

日経ストア - 日本経済新聞社グループの電子書籍ストア
お知らせ・ニュースリリース : 日経ストア
日経ストアは2019年1月6日で販売終了、2019年7月31日をもってサービス終了

オーム社eBook Storeサービス終了のお知らせ - オーム社eBook Storeのブログ
オーム社eBook Storeは2018年2月28日にサービス終了した。

最近でも電子書籍ストアのサービス終了・閉鎖は結構起きてるんだな……
DRMのないPDFであれば、買ってダウンロードしたものは引き続き読めるはずだ。

それでは。