PySparkでコントロールブレイク処理
お題は次のエントリです。
上記エントリではいわゆるコントロールブレイク処理(ソート済みのレコードを読み込み、キー項目ごとにグループ分けして行う処理のことでキーブレイク処理と呼ぶことも)を 1 本の SQL でスマートに行っています。これと同じことを PySpark でやってみるという話です。
次のような CSV ファイルを用意しておきます。
sales_date,jan_code,sales_cnt 2014/10/06,AAA,100 2014/10/07,AAA,200 2014/10/08,BBB,100 2014/10/09,BBB,150 2014/10/10,BBB,189 2014/10/11,CCC,120 2014/10/12,CCC,111 2014/10/13,AAA,210 2014/10/14,AAA,545 2014/10/15,AAA,90 2014/10/16,CCC,90
これを Spark DataFrame に読み込みます。
from pyspark.sql.types import StructType, StructField, StringType, IntegerType, DateType schema = StructType([ StructField('sales_date', DateType()), StructField('jan_code', StringType()), StructField('sales_cnt', IntegerType()) ]) df = spark.read.csv('<path-to-csv>', schema=schema, header=True, dateFormat='yyyy/MM/dd') df.show() # +----------+--------+---------+ # |sales_date|jan_code|sales_cnt| # +----------+--------+---------+ # |2014-10-06| AAA| 100| # |2014-10-07| AAA| 200| # |2014-10-08| BBB| 100| # |2014-10-09| BBB| 150| # |2014-10-10| BBB| 189| # |2014-10-11| CCC| 120| # |2014-10-12| CCC| 111| # |2014-10-13| AAA| 210| # |2014-10-14| AAA| 545| # |2014-10-15| AAA| 90| # |2014-10-16| CCC| 90| # +----------+--------+---------+
元の SQL では ROW_NUMBER
ウィンドウ関数を使って単純ソートした場合の連続値と jan_code で区切りつつソートした場合の連続値を割り振っていますが、PySpark (Spark SQL) でも pyspark.sql.functions.row_number という同じ関数があります。
from pyspark.sql import Window from pyspark.sql.functions import row_number # SQLでの ROW_NUMBER() OVER(ORDER BY SALES_DATE) に相当 df = df.withColumn('simple_sq', row_number().over(Window.orderBy('sales_date'))) # SQLでの ROW_NUMBER() OVER(PARTITION BY JAN_CODE ORDER BY SALES_DATE) に相当 df = df.withColumn('part_jan_sq', row_number().over(Window.partitionBy('jan_code').orderBy('sales_date')))
パーティションとソート順は pyspark.sql.Window クラスのファクトリメソッドを使って生成する pyspark.sql.WindowSpec
オブジェクトとして渡します。
あとは distance を計算すれば集約カラムが作られますね。
from pyspark.sql.functions import col df = df.withColumn('distance', col('simple_sq') - col('part_jan_sq')) df.orderBy('sales_date').show() # +----------+--------+---------+---------+-----------+--------+ # |sales_date|jan_code|sales_cnt|simple_sq|part_jan_sq|distance| # +----------+--------+---------+---------+-----------+--------+ # |2014-10-06| AAA| 100| 1| 1| 0| # |2014-10-07| AAA| 200| 2| 2| 0| # |2014-10-08| BBB| 100| 3| 1| 2| # |2014-10-09| BBB| 150| 4| 2| 2| # |2014-10-10| BBB| 189| 5| 3| 2| # |2014-10-11| CCC| 120| 6| 1| 5| # |2014-10-12| CCC| 111| 7| 2| 5| # |2014-10-13| AAA| 210| 8| 3| 5| # |2014-10-14| AAA| 545| 9| 4| 5| # |2014-10-15| AAA| 90| 10| 5| 5| # |2014-10-16| CCC| 90| 11| 3| 8| # +----------+--------+---------+---------+-----------+--------+
集約のためのキーができたので、集約を行っておしまい。
grouped_df = df.groupBy(['jan_code', 'distance']) \ .agg(min('sales_date').alias('sales_date_first'), \ max('sales_date').alias('sales_date_last'), \ sum('sales_cnt').alias('cnt_sum')) grouped_df.orderBy('sales_date_first').show() # +--------+--------+----------------+---------------+-------+ # |jan_code|distance|sales_date_first|sales_date_last|cnt_sum| # +--------+--------+----------------+---------------+-------+ # | AAA| 0| 2014-10-06| 2014-10-07| 300| # | BBB| 2| 2014-10-08| 2014-10-10| 439| # | CCC| 5| 2014-10-11| 2014-10-12| 231| # | AAA| 5| 2014-10-13| 2014-10-15| 845| # | CCC| 8| 2014-10-16| 2014-10-16| 90| # +--------+--------+----------------+---------------+-------+
PySparkでの時刻変換色々
最近はデータエンジニアリングのお仕事がメインで、もっぱら PySpark を触っています。 自分向けの備忘録的も兼ねてちょいちょい blog に tips を書いていきたいと思います。
今回は時刻変換に関するもの。
タイムゾーン付き日付文字列をパースしてtimestamp型に変換
基本は to_timestamp 関数を使います。
from pyspark.sql.functions import col, to_timestamp df = spark.createDataFrame([('2021-05-16T23:03:49.220Z',)], ['str_datetime']) df = df.withColumn('datetime', to_timestamp(col('str_datetime'), "yyyy-MM-dd'T'HH:mm:ss.SSSX"))
日時フォーマットのパターン文字列は Java方式 です。Spark は Scala で作られているので、Python でコードを書いていてもこういうところで Java が顔を出してきます。
似たものとしてUNIX時間に変換する unit_timestamp という関数がありますが、こちらはミリ秒以下が切り捨てられることに注意してください。
from pyspark.sql.functions import unix_timestamp from pyspark.sql.types import TimestampType df = df.withColumn('time', unix_timestamp(col('str_datetime'), "yyyy-MM-dd'T'HH:mm:ss.SSSX").cast(TimestampType()))
日付文字列をdate型に変換
to_date 関数を使います。
from pyspark.sql.functions import to_date df = df.withColumn('date_col', to_date(col('str_datetime'), "yyyy-MM-dd'T'HH:mm:ss.SSSX"))
ゾーン情報を使ってローカル時刻にタイムスタンプをずらす
from_utc_timestamp 関数を使います。 現地時刻に変換した文字列を取得したい時やタイムゾーン情報のないデータベースに登録するときとかに使うかも。
from pyspark.sql.functions import from_utc_timestamp df = df.withColumn('local_time', from_utc_timestamp(col('time'), 'Asia/Tokyo'))
date型、timestamp型のカラムを手動作成する
Python の datetime.date
型で値を投入すると Spark SQL の DateType になります。
同様に datetime.datetime
型で値を投入すると Spark SQL の TimestampType になります。
import datetime a_date = datetime.date(2022, 1, 1) a_datetime = datetime.datetime(2022, 1, 1, hour=1, minute=10, second=10, microsecond=100000) df = spark.createDataFrame([(a_date, a_datetime)], ('date_col', 'time_col'))
SoftBank回線でSIMフリースマートフォンに乗り換えたら大変だった話
前置き
これまでスマートフォンとして 3 年前に購入した Pixel 3 XL を使い続けていましたが、さすがにバッテリーがへたってきており、もうすぐサポート期間の終了を迎えることもあって別の機種に乗り換えることになりました。
もうすぐ登場する Pixel 6 を当初は考えていたのですが、非常に高価になるという話で、また端末サイズも今使っている Pixel 3 XL よりさらに大きくなるという話なので、別のものも探してみることにしました。
色々探してみたところ、ASUS からリリースされたばかりの Zenfone8 が良さそうということでこれを購入しました。
購入判断の根拠は次のようなところです。
- Qualcomm の最新でハイエンドチップである Snapdragon 888 を搭載していながら税込みで8万円を切るという安さ
- 今使っている Pixel 3 が Snapdragon800番台だったので、800系以外はあり得ませんでした
- iPhone 並のパフォーマンスが欲しかったら700番台、800番台は必須だと思います(特にゲームで大きな差が出る)
- 端末サイズがコンパクトでありながらパンチホールカメラ+ナローベゼルで 5.9 インチの画面サイズを確保している
- SIM フリーなのでキャリアの余計なアプリとかが入っていない
- SIM フリー端末にしては珍しく FeliCa を搭載している
- Suica に全面的に依存していたのでこれは必須
SoftBank回線でSIMフリースマートフォンを使う時の注意点
前置きが長くなりました。ということで家電量販店で上記端末を購入、そのまま家に持ち帰って家で Pixel に指していた SIM を指してセットアップ、その日は問題なく使え、新端末の快適なパフォーマンスを楽しんでいました。
が、次の日に異変に気付きます。外出時に使ってみるとネットワークに一切つながらないのです。幸い電話は使うことができたのですが、外出中は久しぶりに Wi-Fi 乞食になってしまいました。 *1
もしかしてやっぱり契約変更とかが必要なのかな?と思って家から近い SoftBank ショップで見てもらったのですが、「SIM はそのまま指して使えるはず」「SIM カードや設定には特に問題がない」「端末に問題がありそうだから購入したお店で端末を見てもらって」との回答で解決に至りませんでした…。
仕方がないので翌日端末を購入した家電量販店で見てもらうことにしたのですが、そこで実は SoftBank の SIM カードは大きく分けて次の 3 種類があることを教えてもらいました。 *2
自分が今まで使っていたのは 2 番の SIM カードだったので、3 番への交換が必要だったのでした。家電量販店にはキャリアショップもあったので、そちらに案内してもらい、様々な契約変更を行って、無事にネットワークにつながる SIM が手に入りました(応対した人は別店舗での不手際について申し訳ないとかなり恐縮してました)。
こんな感じでちゃんと使えるようになるまでに色々バタバタしてしまいました。この辺りのガイドラインは Web サイト等できちんと案内して欲しいところです。まあできればキャリアから販売している端末を使ってもらいたいからなんでしょうけど。同じようなことをしようとする人がもしいたら参考になるかと思い、今回の顛末をまとめることにしました。
お陰様で新端末ではこれまで使えなかった5G回線やVoLTEも使えるようになりました。
Zenfone 8について
最後におまけで Zenfone 8 の感想を。
IntelliJ IDEA 2020.2でJavaFXのランタイムが同梱されなくなりました
IntelliJ IDEA の最新版 2020.2 がリリースされましたね。日本語でも新機能について案内する記事がアップされています。
この記事の最後のように次のような個人的に気になる記述がありました。
まず多くの人は「JCEFって何?」ってなると思います。これはアプリケーションに Chromium を組み込めるようにするためのフレームワーク CEF (Chromium Embedded Framework) の Java ラッパーのことです。
- CEF のプロジェクトページ - https://bitbucket.org/chromiumembedded/cef
- JCEF のプロジェクトページ - https://bitbucket.org/chromiumembedded/java-cef
IntelliJ は IDE 内部で使う Web ブラウザコンポーネント (Markdown エディタの HTML プレビューなどで使っています) として今後は JCEF を使うようになったということです。これまでは IDE 内部で使う Web ブラウザコンポーネントとしては JavaFX の WebView を使っていましたが、2020.2 でこれをやめることになります。本件について JetBrains の Blog 記事で説明がありました。
ご存じの通り IntelliJ は Swing をベースに作られています。従って JavaFX の WebView は JFXPanel を通して使うことになりますが、そのために性能やレンダリングの問題を解決できなかったとのことです。
実際、JavaFX と Swing はレンダリングスレッドもタイミングも異なり、Swing アプリの上で JavaFX ノードを描画するときは JavaFX のレンダリングエンジンである Prism エンジンではなく Java2D を使って描画するため、JavaFX のパフォーマンスを 100% 発揮することができないのも確かです。この不整合を解決しようとする計画も過去にはありましたが、 Oracle の JavaFX へのやる気が無くなった ので...。
というわけで上記の記事では IntelliJ のプラグイン開発者に対して今後は JavaFX への依存をやめ、Web ブラウザコンポーネントを使いたいときは JCEF を使うことを促しています。
ですが、JCEF は現在絶賛開発中のステータスで、ユーザーとして利用できるような段階にありません。ビルド方法のガイドはありますが利用方法のガイドはなく、API ドキュメントもありません。ラップ対象の CEF については既に利用可能なステータスにあり (C++ のライブラリです) 、ある程度ドキュメントもあるので、こちらを参照して利用方法を推測することになります。
さすがにこの状態で使うのは辛いので、JetBrains 側でラップした API をプラグイン開発者向けに用意したようです。以下のドキュメントで解説されています。
というわけで IntelliJ IDEA 2020.2 からは JavaFX のランタイムは同梱されなくなります。この点について個人的に1点気になることがありました。
実は IntelliJ は Java IDE の中で唯一 JavaFX Scene Builder を内部に組み込んでいた IDE でした。JavaFX のランタイムが同梱されなくなる 2020.2 ではどうなってしまうのでしょうか?
というわけで早速 FXML ファイルを開いてみました。すると...
「Scene Builder Kit をダウンロードしてくれ」というリンクが表示されました。このリンクをクリックすると次に JavaFX Runtime のダウンロードが求められ、それも行うと次のように Scene Builder が表示されました。
というわけで別途ランタイムをダウンロードする必要があるものの、Scene Builder が使えなくなったわけではないのでご安心ください。
以上、誰も気付かないであろう IDEA 2020.2 の変更点についてのお話でした。
横須賀軍港めぐりに乗ってきました
これまで横須賀には 2 回ほど遊びに行ったことがありましたが、今回初めて横須賀軍港めぐりのフェリーに乗ってきました。
文字通り横須賀の軍港をぐるっと一周するクルージングで、ヴェルニー公園からは見えないところ (特に米軍基地のところ) を見ることができます。
写真を色々撮ったのでここにまとめてアップしました。
最初に見えるのは米軍の場所に停泊させてもらっている海自のおやしお型潜水艦。これはヴェルニー公園からも見えますね。
添乗員からの説明が無かったのですが、米軍側のところに停泊していた古びた船が目に付きました。これは宿泊艦だそうで、横須賀の地元の人にはお馴染みの風景だとか。
ご存じ、海自のヘリ空母いずも。やっぱりでかかった。
こちらは米海軍が利用しているところ。アーレイ・バーク級駆逐艦達がずらっと並んでいます。
空母ロナルド・レーガンも停泊していました。そしてその手前を隠すかのようにタイコンデロガ級巡洋艦達がずらっと並んでいました。添乗員の説明によると米空母は機密の塊なので余り近くには寄らせてくれないそうです。
空母ロナルド・レーガン。やや遠目ですが、それでもでかい!
何やら四角いブロックが並んでいたのですが、これは船の消磁のためのものだそうです。日本では横須賀にだけあるのだとか。遠目には大きなコンテナ船が見えますね。
掃海母艦うらが。この船も大きかったです。
最近就役した潜水救難艦のちよだです。確か墜落した F-35 の捜索に加わっていたはずですがもう帰ってきてたんですね。
手前は海洋観測艦のしょうなんです。そして奥にいる艦番号が消された船は退役した旧ちよだです。まさか新旧ちよだを同時に見られるとは思わなかったのでちょっと感激しました。 *1
ずらっと並んだ汎用護衛艦達。左からいかづち、あまぎり、ゆうぎりです。むらさめ型のいかづちが一回り大きいのが分かりますね。
新鋭のそうりゅう型潜水艦も見ることができました。舵がX型になっているのが特徴的です。
こんなのも見られました。日産の工場と、そこで生産された自動車を運ぶためのコンテナです。
手前からてるづき、むらさめ、たかなみ、補給艦のときわ、そしてイージス艦のきりしまです。
きりしまは錨に塗装をしている珍しい場面に遭遇しました。
こんな感じで陸側からは見ることができない様々な風景を見ることができました。軍艦好きなにはお勧めのクルージングなので、横須賀に寄った際にはぜひ。
*1:細かいところですが、旧ちよだには喫水線部に黒帯の水線塗装がされていましたが、新しいちよだにはありませんでした。なんでだろう?
とにかく簡単にEmacsのフォントを設定する方法
最近仕事場のマシンを新しくし、Mac をやめて Windows にしました。でもエディタはどの OS でも Emacs を使う人間なので、Windows にも Emacs をインストールし、その際に久々に Emacs の設定を色々いじったのですが、フォントについてもプログラミング向け等幅フォントを使うように設定したりしました。
Emacs の設定で良く鬼門とされるのがフォント設定です。Emacs は文字集合別にきめ細かくフォントを設定できるようになっているので、よく知らない人にはすごく難しいように思われています。でも、実は最近の Emacs ではとりあえず Ascii と日本語の文字集合に対してサクッとフォントを設定すればいいのならば実に簡単に設定できるようになっています。Emacs のフォント設定をぐぐっても小難しい方法ばかり出てきて、一番簡単な方法にたどり着かない人が多いのではないかと思い、メモっておくことにしました。
メニューから [Options] - [Set Default Font...] を選択します。
すると、次のようなフォント選択ダイアログが出てきます。ここで使いたいフォントを選択します。日本語と Ascii 文字で同じフォントが使われるようにするため、[欧文] に対して選択します。この例ではプログラミング向けフォントとして割と定番の Takaoゴシック を選んでいます。
選択すると Emacs のフォント設定が変わります。ここで再びメニューから [Options] - [Save Options] をクリックして設定を保存します。
以上です。
え、 .emacs ファイルに S 式をごちゃごちゃ書くんじゃないの? と思われた方がいるかもしれません。実は Emacs はこのように GUI で結構設定変更ができるように進化しているのです。「Emacs って小難しい設定ファイルをいじくり回さないといけない古くさいエディタなんでしょ?」なんてと思っている方は認識を改めてください!
とは言え、この設定結果は結局 .emacs に書き込まれます。non ascii な文字が入っているフォントファミリーを選ぶ場合、.emacs には予めマジックコメントを入れてファイルエンコーディングを明記しておいた方がいいかもしれません (やっぱり .emacs 覗かないといけないじゃねーか、って突っ込まれそうですがw) 。
;;; -*- coding: utf-8 -*-
なお、「自分はあくまで .emacs をいじる派だ!」という方は次のように default-frame-alist
変数にフォントファミリーと文字の大きさをハイフンでつなげて設定すれば OK です。ざっくり設定ならばこれで十分です。
;; Initial frame settings (setq default-frame-alist (append (list '(font . "Takaoゴシック-10")) default-frame-alist))
以上、Emacs のフォント設定を行う一番簡単な方法についてでした。
JavaFX 11に追加されたRobotクラスの紹介
このエントリは じゃばえふえっくす Advent Calendar 2018 の 3 日目のエントリとしてぶっ込みました。もうクリスマスは終わってますが、まだ空いていますし、 1 日目のエントリ で次のようなことを書いていたのをまだ放置していたので、それを拾うエントリとなります。
上記の Maven リポジトリをご覧になると分かりますが、Java FX 11 もリリースされました。リリースノートは次の通りです。
https://github.com/javafxports/openjdk-jfx/blob/jfx-11/doc-files/release-notes-11.md
まあ新機能は少ないのですが、
Spinner
コントロールに改善が加えられていたり (詳しくは Yucchi_jp さんのこのブログエントリ を) 、新たに Robot というクラスが追加されています。このRobot
クラスについては別のエントリで触れる予定です。
というわけで、JavaFX 11 になって新たに追加された Robot
クラスについて触れたいと思います。
Robot
クラスはプログラムから画面操作を行うために必要なメソッドが実装されたクラスです。あたかもロボットが画面操作を人の代わりに行うようなことを実現するというわけです。 AWT にも同様のクラス があります。一番のユースケースは GUI の自動テストを実装することで、それを実現するために次のような操作を実行するためのメソッドが実装されています。
- マウスの各種操作 (移動、クリック、スクロールホイール) を行う
- マウスの現在位置を取得するためのメソッドもあります
- キーボードをタイプする
- タイプとは別にプレスとリリースにもメソッドが用意されています
- 画面のスクリーンショットを撮る
- 指定した座標の色を取得する
モジュールは javafx.graphics
に、パッケージは javafx.scene.robot
に入っています。
画面を操作するのに必要なものは一通りそろっているので、これで定形作業を肩代わりするプログラムを作るのに使えますね。ゲームの周回操作とかにもいいかも。
実はこのクラス、昔からあったのですが internal なクラスとなっていました。このクラスを使って自動テストフレームワークを作っていたライブラリが多く、Java のモジュール化に伴い「外に出してほしい」というリクエストが非常に多かったのですが、11 になって晴れて表に出てきてくれました。
ところで以前、自分の書いた次のエントリで、JavaFX で xeyes のクローンを作った話をしました。
そこでこんなことを書いていました。
てなわけで、早速この Robot
を使って "Pure JavaFX" なアプリにしちゃいましょう。
まずは 10 以前のマウスポジションを取得するコード。
private void updateMousePosition() { SwingUtilities.invokeLater(() -> { final Point pointerLocation = MouseInfo.getPointerInfo().getLocation(); Platform.runLater(() -> updateEye(pointerLocation.x, pointerLocation.y)); }); }
Swing の EDT と JavaFX のアプリケーションスレッドを行ったり来たりして見通しが悪いですね。これを 11 の Robot
クラスを使うように変更します。使い方は簡単で、普通にインスタンスを取得すればいいです。Controller の初期化処理で取得しておきます。
private Robot robot; @Override public void initialize(URL location, ResourceBundle resources) { robot = new Robot(); // (以下略)
後はこのインスタンスを使ってマウスカーソルのグローバルポジションを取得するだけです。 updateMousePosition()
メソッドの内容が次のように変わりました。
private void updateMousePosition() { updateEye(robot.getMouseX(), robot.getMouseY()); }
はい、これで複数スレッドを行き来する必要がなくなりました。しかも java.desktop
モジュールへの依存も無くなるので、配布 JRE のイメージサイズも小さくすることができますね。 *1
少し付け加えると、 Robot
から取得するマウス座標の値は AWT と違って double
型になります。なので updateEye()
メソッドの引数の型を変える必要がありました。
というわけで JavaFX 11 の新機能である Robot
クラスの紹介でした。さすがに今年の Advent Calendar への投稿はこれで終わりかな。皆様良いお年を。