子供の落書き帳 Renaissance (original) (raw)

「最後の英単語:約20000語の英単語リスト」というものがインターネット上にある。
https://l-formula.com/last-words
今回、このリストのうち1000語をAnkiを使って単語カードにして覚えた。その結論がこれだ。

結論: この単語集を使うのはやめとけ

これだけ覚えて帰ってくれれば問題ないです。
どこがどう良くないのかを詳しく知りたいという人向けに続きを書いていきます。

「最後の英単語」を使って単語を覚えた状況について

使い始めた動機について。 どこで初めて知ったのか今となっては思い出せない……
無料で入手できる大学レベルの英単語一覧とその語彙レベルあたりを見ていて、「10000語 英単語リスト」とかでいろいろ調べていて、偶然見つけたんだと思う。

使い方については、今年(2024年)の1月にAnkiに入れて、ひたすら英語→日本語を回した。
(Ankiでの学習方法については、この記事で紹介しないので、他の人の記事を見てください。)
以下の記事を書いたときに単語数を測定したときには9200程度だったので、9001〜10000が妥当かなと思い、この1000語を覚えた。
linus-mk.hatenablog.com

さてこのサイトの説明には、

総計19511単語の英単語について、英単語・意味・発音記号を重要度順に並べた英単語のリストです。

と書いてある。英単語が1つ、それについて和訳を表示、というシンプルな形式である。
したがって、(発音記号を無視した場合)この英単語リストが良くないと批判する理由は、英単語の選び方がおかしいか、和訳がおかしいか、その両方か、である。
そして実際、その両方なのだ。
というわけで、以下「英単語の選び方がおかしい」「英単語の和訳がおかしい」に分けて詳述する。 (以下の説明中で、9001〜10000はサイト上の単語に付記されている番号である)

英単語の選び方がおかしい

全体的なレベル感

フォローしておくと、9001〜10000のレベルは平均的には英検1級くらいの語彙が多いと思う。 例えば、以下は英検1級の単語帳(EX英単語帳)にも登場する単語である。

私が英字新聞を読んでいたりして一度調べた単語(exuberance, flimsyなど)もあり、 全部が全部ダメというわけではない。一部の単語がダメなのだが、その一部が決して少なくない割合なのである。

固有名詞が少なからずある

国名と地名が結構ある。あと神話の人物が結構ある。こんな感じだ。英単語の勉強ではなく地理か神学の勉強になってしまうから、サッサと学習対象から除外した。

異常に簡単な単語がある

この9001〜10000のセクションにあった中で最も簡単な単語は、おそらく、bus, menu あたりだろう。
普通に英単語を覚えていって、9000単語覚えるまでにbusとmenuを覚えてないってこと、あるか??

あとは派生形の単語を派生元の単語と1つにまとめなていないので、元の単語を知っていれば容易に推測できる単語がやたらとある。
winner(9059)、buyer(9112)、normally(9302)、undeveloped(9806)などなど。このへんはわざわざ覚えなくても良いでしょう。

異常に難しい単語がある

かと思うともう一方で、やたらと難しいというかマニアックな単語がある。

など。気合で覚えたけど、これが文章中で出てくることはあるんだろうか。古代ギリシャに関する文献を読まないとほぼ出てこないと思うが。 thrush(ツグミ)もあったが、別に俺は鳥類学者になりたいわけじゃないんだが……。

ちなみに何に基づいてこの単語が並んでいるかというと、以下のように説明されている。

単語選定の方法
Project Gutenbergを用いたRank決定
Project Gutenbergという、著作権の切れた文書をインターネット上に公開するプロジェクトがあります。
これを利用して、単語の出現数を調べ、頻出のものが重要になるようにしてあります。
https://l-formula.com/last-words

基本的には、文章がたくさんあり、その中での単語の頻度順に並んでいるということだろう。
busとmenuとphalanxとchancelが同程度の頻度で出現する文章、どういうものだろうか。

ここまで「英単語の選び方がおかしい」話であった。 しかしこれに関しては、「変な単語だな」と思ったら、Ankiを使っている場合は除外すれば問題が出なくなるからまだ良い。 ここからは英語に対応する日本語がおかしいという話なので、そうもいかない。

和訳がおかしい

和訳がおかしい話。 なお、この「最後の英単語」がどこから和訳を利用しているかという話については、最後のセクションで述べる。

誤字が非常に多い

中でも一番目立つのは誤字である。こんなに誤字脱字が多いことってある? と言いたくなるくらい多い。
1000単語の中で30個以上は見つけた。まだあるかもしれない。

番号 英単語 サイト上の和訳(一部抜粋の場合あり) 誤字訂正内容
9034 enjoyable 楽しろい,愉快な,楽しめる 楽しろい→楽しい
9072 lunar 年の,年に関連する 年→月
9096 bruise 打に傷(あざ)をつける;……(中略)打ち傷がつく 打に傷→打ち傷
9097 left-hand 《名詩の前にのみ用いて》 名詩→名詞
9129 spar (ボクシングの練習のために)ハパーリングをする ハパーリング→スパーリング
9217 ballast (気救の)砂袋 気救→気球
9311 confusing 乱混させる[ような],当惑させる 乱混→混乱
9341 saffron サフラン/サフラン自の 自→色
9347 homesick ホームショクの ョ→ッ
9365 artery (道路・水路・鉄道などの)勘線 勘線→幹線
9367 bribery 贈賄(ぞうわい);周賄 周賄→収賄
9370 terrify おびやすか おびやすか→おびやかす
9384 talker 話し手 / おじゃべりな人 おじゃべり→おしゃべり
9387 insertion 挿入物;(新物の)折り込み広告 新物→新聞
9404 debauchery 《通例複数形で》らんちぎ騒ぎ らんちぎ→らんちき(乱痴気)
9411 stuffing (枕なとの中に入れる)詰め物 と→ど
9440 enhance …‘の’程度(仮値など)を高める 仮値→価値
9473 masquerade 仮面舞踏班に出る 班→会?
9495 impiety 不信心,分敬,分孝 分→不
9643 mettle 気性,気質 / 件気,勇気 件気→血気
9681 collateral 担保,低当,見返り物資 低当→抵当
9714 scamp ならず者 / いたずら者,わんぱく小訴 小訴→小僧
9724 magnify 〈レンズなどが〉…‘を’『拡大うる』 う→す
9803 billiards 玉突き,撞救,ビリヤード 撞救→撞球
9825 baleful 有割な;悪意のある 有割→有害
9833 perversion 墜落,邪道,変熊 墜落→堕落、熊→態
9839 usury 高利貸し業 / (法外な・違な)高利 違→違法
9857 baffle 〈人〉’を’除方に暮れさせる 除方→途方
9884 cobbler 鞍直し職人 コブラー(フルーツパイの一種) 鞍→靴
9880 notch 〈競技の得殿など〉’を’記録する 得殿→得点
9892 insoluble 浴解しない 浴→溶
9906 shred (特に細長い)切れ端;断語 断語→断片?
9941 lurk 『潜む』,潜状する 潜状→潜伏
9955 gable 破風(はふ),切り妻(屋根の斜目を2辺とした三角形の外壁部) 斜目→斜面?
9994 unmindful (…を)気にかけない,(…に)むとなじゃくな むとなじゃく→むとんじゃく
10000 pewter 白目(しろめ)(すずめを主成分とした合金;昔,台所用品に用いた) すずめ→すず(錫)
9692 infusion 振り出し汁 ???

mettleの「件気」は「元気」の誤りに見えなくもないが、英辞郎を引いたら「血気」という訳語が出たのでこれの誤字と解釈した。

何でこんなに誤字脱字が多いのか気になりすぎたので、パターンを分類してみた。(ここの矢印は、上の表とは逆で正しい字→誤字 の順。)

どういうプロセスでこの辞書は作られたんだろうか……??
ある辞書に対してOCRを適用したならば「読みが同じ漢字(字形は異なる)」に変化することはないだろう。逆にパソコンに手で打ち込んでいったならば、「字形が似た漢字(読みは異なる)」がありえないと思うのだが。全くもって謎である。

誤字脱字が大量にあるが、その中で一番ヤバいのは lunar 「年の」である。 俺はこの単語帳をやる前の時点で「lunar = 月の」だと知っていたので、間違いに気づいたが、何も知らない人がこの単語帳で勉強したら、「lunar = 年の」だと信じてそれで覚える可能性が高い。

2番目に同様にヤバいのは「cobbler」である。 lunarとは違って、俺はこっちの単語を知らなかった。ただ「鞍直し職人」という(実は間違っている)訳語を見た俺が、「いや鞍直し職人ってなんだよ。馬の鞍を直すの専門なのかよ」と思って各種辞書を調べたら、こう出てきた。

cobbler 【名】 靴屋、靴修理屋英辞郎

A cobbler is a person whose job is to make or mend shoes.COBBLER definition and meaning | Collins English Dictionary

おい!鞍じゃなくて、靴じゃないか! 危うく「lunar」と同じように間違ったほうで覚えるところだった。

訳語が妙に多い

番号 英単語 サイト上の和訳全文
9264 tilt …‘を’傾ける / (馬上槍試合で)〈槍〉‘を’突き出す,〈相手〉‘を’槍で突く / 傾く / 馬上槍試合をする / (相手を)槍で突く《+at+名》 / (文章・言葉で)(…を)攻撃する《+at+名》 / 傾き,傾斜(slope) / (中世騎士の)馬上槍試合 / (一般に)対決,試合
9622 graze 〈家畜が〉『牧草を食う』,草を食う / 〈家畜が〉(草などを)食(は)む《+『on』+『名』》 / 〈家畜〉‘に’『牧草を食べさせる』;〈家畜〉‘を’放牧する / 〈草原〉‘を’牧場に使う / 〈家畜が〉〈生草〉‘を’食う …‘を’かする / …‘の’皮膚をすりむく / (…を)かすめて通る《+『along』『by』,『past』)+『名』》 / (…で)こすってすりむく《+『against』+『名』》 / 〈U〉〈C〉かすめて通ること / 〈C〉すりむいた傷

tiltは「英検1級 単熟語EX」にも載っているが、その訳語は「動 (人・ものが)傾く;(もの)を傾ける / 名 傾く[傾ける]こと」である。当然ながら馬上槍試合のことは書いていなかった。 grazeは大きく2つの意味があるから長くなりがちだが、それにしても同じような訳語が何度も書いてある。

訳語が妙にマニアック

番号 英単語 サイト上の和訳全文
9451 lettuce レタス,チシャ
9998 bale (輸送または貯蔵用に包装した))…の)大包み,こり,俵《+『of』+『名』》 / …’を’こりにする,俵に入れる =bail

チシャってなんだ!?と思ったらレタスの和名(萵苣)らしい。それは「レタス」だけ書いておけば良いんだよ。全員それでわかるから。
「こり」を知らなかったのでまた誤字かと思ったが、「梱」と書いて「こり」と読むらしい。英和辞典を引いてもだいたいこの訳語なので、これで合っているのだろうが……わかりにくいね。

訳語が妙に少ない

番号 英単語 サイト上の和訳全文
9795 exuberance 豊富,充満;繁茂
9828 glue 『にかわ』,にかわ剤;(一般に)接着剤 / …‘を’にかわで付ける / 《しばしば受動態で》(…に)…‘を’くっ付けて離さない(離れない),〈視線など〉‘を’くぎ付けにする《+『名』+『to』(『on』)+『名』》
9876 ingredient (混合物の)成分,原料 / 構成要素

探せばもっとある気がするが、もう書ききれないのでこの辺で終わりにしよう。

発音が間違っている

「英単語の選び方がおかしい」「英単語の和訳がおかしい」の2つだけと言ったな、あれは嘘だ。発音の誤りも見つけてしまった。

番号 英単語 サイト上の発音記号
9881 ruse rú:sei

総評:ただより高いものはない。単語帳の本を買いましょう。

2024年の4月からは英検1級のEXをつかって単語を覚えているが、さすがに単語の選び方・訳語ともしっかりしていて、いちいち疑わずに安心して覚えられる。 タダにつられて適当な単語帳を使うと、いちいち調べ直して時間を無駄にしたり、「lunar = 年の」と間違って覚えることになったりするので、 ちゃんとお金を出して単語帳を買ったほうがよいということを身をもって思い知った。

出る順で最短合格!英検®1級単熟語EX 第2版 (出る順で最短合格シリーズ)

この妙な誤字はどこから来たのか(どの辞書を使っているのか)

さて、一体この妙な和訳の元ネタは何なんだろうか?

この「最後の英単語」とは無関係に、Amazonで なりしか「極限の英単語」のレビューを見ていたら、驚くべき指摘を見つけた。

cobblerが「鞍直し職人」となっているが鞍じゃなく靴ではなかろうか
https://www.amazon.co.jp/gp/customer-reviews/R28DXE7VDZTVXF/ref=cm_cr_dp_d_rvw_ttl?ie=UTF8&ASIN=B074MD9QCB

なに〜〜〜!? cobblerを「鞍直し職人」と間違って書いてあるのは、この「最後の英単語」と なりしか「極限の英単語」で共通しているのか!? となると、両者に共通する元ネタとなる辞書があるはずだ。

「cobbler "鞍直し職人"」で検索すると、4件ヒットする。2件は「最後の英単語」と「Amazonのレビュー」だから、残りは2つだ。

http://eigoyasan.blog.fc2.com/blog-entry-2553.htmlからたどっていくと、

辞書データはネット上に公開されているejdicの修正版を使わせていただきました(出所:無料 英和辞書データ ダウンロード @WEB便利ツール by クジラ飛行机様)。

と書いてある。

いまはEnglish-Japanese Dictionary "ejdict-hand" というGitHubリポジトリになっている。
いままで述べてきた誤りを全部確認はしていないが、 * 単純な誤字については殆ど直っている。(昔に「最後の英単語」の方には反映されていないので、俺が作ったAnkiカードには大量の誤字が入っている) * ただし、訳語を見て誤字と分からないものは直っていない。 * lunarは「年の」だしlunar eclipseは「年食」だ。* cobblerも間違った訳語「鞍直し職人」だ。

元となったデータは「ejdic」もしくは「ejdict」という名前らしい。
PrepTutorEJEICについて を見ると、古くからあり起源は不明らしい。
GitHubのデータは誤字が修正され、だいぶ良くなっているが、上記のような誤りが混入していることには注意した方が良い。

それでは。

英検準1級に合格した

2023年第3回試験
1月21日(日)1次試験
3月3日(日)2次試験
3月12日〜 合否発表(ネット上)
というわけで確認したら受かってました。

結果

特にリーディングは700/750って書いてあるから、CEFRのC1レベルに届いているな。 (準1級だとC1達成と判定しないので、結果ページのCEFRではB2ってことになっているが……)

B2の下限が英検CSEスコア2300、C1の下限が2600。俺の現在スコアが2487(下限+187点)。
ということはB2の中でも下から6割くらいのところか。

1次試験

筆記試験90分の時間配分をメモっていたのでここに書いておきます。最初の問題から順に解いています。

ただリーディング大得意で読むの速い人の記録なので、もう少し長くかかると思います。 これから受ける人は英作文を早めに書き上げられると良いでしょう。

2次試験

さて2次試験について書いていくが……

英検の公式ホームページにも2次試験の過去問は掲載されていない。
第一に質問文を著作権の関係でも問題があるし、第二に正確な質問文は覚えていない。日本語でだいたいの意味を書いています。
(ただ2024年度から試験形式が変わるから、あまり参考にはならないかも?)

着席して軽く話をする。自己紹介は「35歳です、ITエンジニアとしてITコンサルティング会社で働いています」と喋った。
2問目で「休日は何をしていますか」と聞かれることに備えて、どう答えようか事前にあれこれ考えていたが、1問目だけで本題の試験に入った。

4コママンガは高層マンションの話。(詳しくは過去問題集を見てください。)

これを2分で説明する。 2コマ目では営業マンが「I can take you to the construction site.」的なセリフを喋っていた。 これを間接話法で話さなければいけないので、とっさには難しい。 「He told them that he can ... he could take them to the construction site.」と一度言い直した。

4コマ目は citizens are against the construction とか言った気がする。 反対するなら「be opposed to the construction」とか使えば良かったな。

ここから質疑応答が4問入る。1問目はお決まりの「4コマ目の登場人物の心情を答えよ」である。
……さて、後から振り返ると、これは英検準1級の過去の傾向、典型から外れた、結構イレギュラーな4コママンガである。
YouTubeの解説動画を見ると、1コマ目で何か課題が発生します的なことを言っていたと思うけど、今回の1コマ目では明らかに問題は発生していない。
4コマ目で問題が起きたといえば起きたが、そんなに重大な問題でもない。別に家を購入したわけじゃないんだから、

というわけで試験当時に戻ると、俺は困ってしまった。
「反対運動なんか知らない、俺は絶対にここに住むんだ」という極端な賛成も「反対運動が起きているから、ここに住むのは絶対に無理だ、諦めよう」という極端な反対も極端な反対も取りにくいな、と思った。
「ここに住むのは良いと思うが、住民の反対が強いので住むのが難しいのではないか……」的などっちつかずなことを言った。

第2問。 インターネット上のコンテンツは人々がものを買うことに影響しているか、じゃなかったっけ?
これはYESが答えやすいでしょう。

的なことを言おうとしたが、「参考にしている」が全然出てこなくてだいぶ詰まった。

第3問。 最近の若者が政治に関わっているかだね、確か。

なぜか咄嗟にグレタ・トゥーンベリが思い浮かんだので、YESで。

LOGOPHILIAの単語帳で見た「take to the streets」(街頭デモを行う)を使おうとしたが、間違って「take out to the streets」って言った気がする。

第4問。 都会に農業地帯を増やすべきか、だったと思う。

agricultural という単語が聞こえたので農地のことだとは思ったが、咄嗟に論説が組み立てられなくて、ただの緑地として答えてしまった。
都会の人間はストレスが溜まってるから緑を見てリラックスできるので、緑地……and agricultural areaを増やすべきだ、と最後で強引に軌道修正した。

これからどうする?

次は英検1級……だがそのハードルは結構高そう。

英検1級に本当に受かりたかったら全体的に底上げが必要だと思うけど、なんとなく単語帳だけやってしまって試験の日を迎えるような気がしてならない。

出る順で最短合格!英検®1級単熟語EX 第2版 (出る順で最短合格シリーズ)

ちなみに準1級の過去問は↓を使っていました。

2024年度 英検準1級過去問題集

それでは。

2023年に買ってよかったもののメモ。あんまり多くない気がする。

Shokz OpenRun Pro 骨伝導イヤホン

一番はこれだろう。7月のAmazon大規模セールにつられて購入。
今まで有線のイヤホンでリモートワークの会議に参加していたが、パソコンの前にいる必要がなく自由に動けるようになったのは良い。音質も聞きやすい。口の前にマイクはないのだが、ちゃんと話もできる。
ジョギングとの相性も良い(耳をふさがないので、車の音なども聞こえる)。
ただ寝転んだ場合の相性は悪い(頭の後ろが当たるので)。

……そういえば有線で良いイヤホン買ってないな……(Ultimate Ears UE900sは断線で壊れてしまった。あとゼンハイザーのIE60はここ数年間行方不明だ。どこ行ったんだろう……)

Shokz OpenRun Pro 骨伝導イヤホン 最新骨伝導技術 低音再生強化急速充電 DSPノイズキャンセリング・マイク 10時間の音楽再生と通話 公式ストア正規品 ワイヤレス 防水 bluetooth5.1 ブラック

VOLTRX 電動プロテインシェイカ

プロテインを飲み始めたときに、自分で混ぜなければいけないのに面倒くさくなって電動シェイカーを購入。
(自分で振るシェイカーをすっ飛ばして、いきなり電動シェイカーを買った)
ボタンを押すだけなので楽。プロテインの粉がきれいに溶ける。
ただ、物持ちは悪い。2022年末に購入して、数ヶ月したらボタンを押さなくても勝手にスイッチがオンになって「ブーン」と音を立てるようになってしまった。その後、最近は完全に壊れて電動部分が全く動かなくなった。捨てて次を買おう……
考えてみると、これはコップの一種なので水を入れたり洗ったりして濡れることが多い。そして、充電式なのでUSB Type-Cで充電する必要がある。当然、ちゃんと乾かしてから充電するようにすれば問題ないのだろう。しかし、面倒くさがりの俺には無理だった……

VOLTRX 電動シェイカー - VortexBoost プロテインシェイカー ミキサー - USB C 充電 プロテイン シェイカー - BPAフリー、防水、カラフルなライトショー、600ml(オーロラグリーン)

VALX プロテイン

プロテインの方はもっぱらVALXを買っている。
公式サイトで毎月末にセールしているので、無くなってきたら4つセットで買うことにしている。
最初に買ったGronGがあまりにもまずかったので、色々調べたりしてここにたどり着いた。
個人的にはどの味を買っても美味しくてハズレがない。
と思うが、動画とかを見ているとまずいって言ってる人もいる模様。個人によって感じ方に差があるようなので自分にあったプロテインを見つけるしかないね。

最初に飲むならカフェオレ味が無難な気がする。プロテインっぽさが無くて普通のカフェオレと思って飲める。ロイヤルミルクティーとか抹茶も好き。

VALX バルクス ホエイ プロテイン カフェオレ風味 ぷろていん ホエイプロテイン Produced by 山本義徳 1kg 国内製造

ランニングポーチ

ランニングするときにポケットにケータイ入れてると揺れて落ちそうで怖いな……ということで購入。
スマホと鍵とSuicaを入れる。

【日本代表選手が愛用】ランニング ポーチ ウェストポーチ TRAN 伸縮大容量 フィットして揺れにくい スマホ収納 イヤホン穴 鍵入れ・カード入れ別収納 ジョギング・マラソンに便利

SUZURI ドライTシャツ

犬たくさん バックプリント
DDRするときはだいたいこれ。汗でびしょ濡れになってもすぐに乾く。
面白いデザインのドライTシャツ出てこないかな。

ワークマン ボアフリースカーディガン

最後は貧乏くさいやつだが……
サンシャイン池袋に「ワークマン女子」があって、出かけたついでに寄ってみたらフリースが980円でびっくりしたので買ってしまった。
これが1000円を切るってどうなってるんだ。ワークマン恐るべし。家で着ているが、暖かくて良い。
商品ページが見つからなかったので、紹介記事の方でリンクを張っておく。
ワークマンで買える「ボアフリースカーディガン」が、想像以上の快適さだった。コレで980円はさすがすぎる…

総評:やっぱり少ないな

ちゃんと不便や必要性を察知して「これを買おう」と決める力が弱い気がする。
面倒くさがりだからそういう意思決定を避けてしまうのであった。
年間の収支は結構なプラスだったし(マネーフォワードの記録を見た)、もっと積極的に新しいガジェットとかを買ってもいいと思うんだよな。 毎月1万円とか5千円を新しいものを買うのに強制的に充てるとかしたほうが良いのか……
しかし問題は何を買うかよな。例えばYouTubeで「買ってよかったもの」で検索するとたくさん出てくるけど、それが俺に合うかは分からないわけだし。
さてどうしたものか……みんなどうしてるんやろ?
それでは。

2023年振り返り

1月も気づけばあっという間に半分が終了してしまった。
忘れないうちに2023年の振り返りをしよう。

2022年版はこちら。
2022年の振り返り - 子供の落書き帳 Renaissance

※ 今年・去年・来年がややこしくなりそうなので、2023年・2024年で表記を統一します。

仕事

2023年はずっと1つのプロジェクトに従事していた。 12月にリリース・本番稼働ができて、良かったね……と、少なくとも表面的には言えるだろう。 だが本当にうまくやれただろうか。

リリース・本番稼働に向けた各段階において、起こり得る問題を先回りして特定し、関係する他のチームの人たちと適宜相談して課題を解決できた。
色々なところから飛んできた調査依頼や相談については、丁寧に応対してきたし、それによってプロジェクトが前に進むことに貢献できたという自負はある。

じゃあ、何で自分のところの仕様に詳しいのかと考えると、何というか「そのチームに長く在籍しているから」なのだ。 そりゃ長いことその部分に関与してきて、ずっとやっていれば詳しくもなるだろうという当然の道理である。
ちゃんと自分の理解を、他のチームにも分かるようにドキュメントや仕様書として残すべきだったのだが、 納期に間に合わせるためにドキュメント化は犠牲になったのである。 去年の仕事の心残りは、ドキュメントがボロボロだったこと、この1点に尽きる。
自分の強みは、複雑な事柄を読み解いて、他の人にも分かるような適切な形で整理することだと思っている。 その強みがフロー(日々のSlackのやり取り)では活きたものの、永続的なストック(各種ドキュメント)に強みを活かすことが少なかったのは、まぁまぁフラストレーションになっている。

他チームが(俺のチームの担当部分を含む全体の仕様を整理するために)俺のチームの範囲の動作仕様を整理した図を作っていたのとか、恥でしょ。 我々が職務怠慢をしていて他のチームに肩代わりしてもらったことの証左なんだから。今思い出したって忸怩たる気分だ。 というか、ドキュメント不足のせいでどういう仕様なのか俺自身でさえ把握できていない箇所があるしな。

Pythonのコードを自分自身が書くことは殆ど無くなって(少しはある)、人のコードをレビューするのが多くなった。
じゃあプロジェクトマネジメントやチームリーディングを担当しているかというと、そういうわけでも無いし、自分の担当している業務がうまく表現できなくて、いつももどかしい気分になる。
自チームで作っているシステムについて何でもやります、というのが正しい気がしている。

一人暮らし

面倒くさがりな性格のせいで、「一応生きていくことはできるが、使いにくくて不便」という状況になっているので、どうやって住みやすい暮らしにすれば良いかな……

一人暮らしして1年半、この状態が継続中……生活力が皆無であることが露呈するのであんまり具体的には言えないけど。
あと、一人暮らしを脱出できなかったわ。

自己学習

2023年も2022年に続いてほとんどやらなかったなぁ……。

ブログに技術的な内容で書いたのは、この2個か。

確かにこの2つは実際の業務で詰まったから、調べてまとめたんだよな。今でも思い返すことができる。
他の点は行き詰まることが無かったのか……というと、そういう訳では無い。
課題は他にもまだまだあったが、課題の抽象度が上がって、上記2つのような具体的なものとして指し示すことができなくなった。
こにふぁーさんのKonifar's ZATSU みたいな感じで、うまく抽象化して書くのが良いのかなぁ……

connpassのイベントの話

年末に、同僚が「connpassなどの外部の勉強会に積極的に参加している」という話をしていた。 まず第一に「勉強熱心ですごいなぁ」と思ったが、その次に「もっと早く知りたかったなぁ」と思った。 (別に個々のイベントの内容を全部知りたいわけではなくて、(俺を含む)周りの人を感化することができたんじゃないか、的な。)

会社内の周囲の人たちが、(仕事以外に)どういう技術的な活動をしていて、どういうことに興味があるか、が見えにくい。
俺、チーム、プロジェクト、顧客、以上。って感じだわ。

さりとて、どのイベントに行くかって考えると、難しくないかなぁ。ディープラーニングを業務で使っているわけでもないし。機械学習じゃないデータ分析系ってあんまりイベントにならない気がするし。 ん〜〜、Pythonの書き方とかシステム構築の一般論とかそのへんだろうか。

英語だけはやり始めた話

11月あたりから急に英語をやりだした。
きっかけはおそらく、ふるやん(@furuya1223)さんが漢検1級を取ったことだと思う。https://www.creativ.xyz/kanken-1k/

(俺自身が漢検準1級を10年ほど前に取ったので分かるのだが)漢検1級は準1級とは別格の難しさがある。 その割に取っても使い所が殆ど無いという資格である。 あれを1年くらい? それ以上? 長期間にわたって継続して勉強して合格まで行くの、マジすごい。と思った。

俺も何か資格を取るかなーとぼんやり考え始めた。 合格してどれくらい役に立つ/役に立たない目標にしようか色々考えつつ、漢検1級は無理な気がしたので英語にした。 英語使えたほうが何かと便利だし……(既に役に立つ方を目指そうとしてしまっている)
TOEIC満点を目指すのは、意味があんまりない資格なのでそれを目標にしようかな(既に一度975点を取っているので、990点とそう変わらないため)とも思った。 結局、TOEICと別の尺度でやってみたら面白そうな気がしたので、英検で。 ライティングとスピーキングなんて普段全くやらないから、これを機にやるのも悪くないだろう。 2024/1/21に英検準1級(1次)を受けます。準1級の後、英検1級を目指すか、TOEIC満点を目指すかは考え中。

今は英字新聞を読んだり、YouTubeで英語のニュースを聞いたりして勉強しているが、 ちょっと前に勉強した単語が別のところで登場しているのを見たりすると、純粋に楽しくて嬉しい。
やっぱり自分は(ストレングスファインダーでいうところの)学習欲の人間なんだなーと。
「学習欲」が強い人は、すぐに役立ったりしなくても、何かを学ぶときに楽しくなる、的なことが書いてあった気がする。

ゲーム

音ゲー

2023年にプレイした音ゲーは、ほとんどがDDRだった。
skill attack 2022末
skill attack 2023末

レベル14の99万点が1→3譜面、レベル15の99万点が0→1譜面。レベル16の95万点が1→12譜面。 まぁ逆詐称の譜面が新たに増えたという理由も一部あるとはいえ、地力は向上したと思っていいだろうなぁ。

あとはクリア目線、18を弱・中・強の3つに分けたときに弱は大体はクリアできて、中はできたりできなかったり、 クリアできそうでできないのが、Cosy Catastrophe、VOLAQUAS、Triple Journey -TAG EDITION-、このあたり。 というか他の曲はほとんどやってないな。頑張ってクリアするところまで粘着してない。

んー……一応ドラマニも書いておく? でも全然プレイして無かったと思う。
HIGH-VOLTAGE gsv記録
FUZZ-UP gsv記録
新曲を全然詰めてないな。あと旧曲もちゃんとやってないからスキルポイント減ってるな。オワタ。

音ゲー以外

原神が主。
螺旋12層が全然できないのが悩み。多分適当にやっているから戦闘スキルが低い。

ポエム

最近、ぼんやりと「もう少しあの活動に時間と労力をかけていれば、もっと結果を出せていたのかもしれないな」と思うことが多い。 簡単に言えば時間配分のトレードオフというやつだ。
「あの活動」に入るのは、だいたいちょっと取り組んだこと、多分音ゲーDDR)とAI画像生成(Stable Diffusion他)である。
技術的な取り組みが「あの活動」に代入されることがない時点でどうなのよ感がある。結構、ある。技術へのコミットメントを増やしたいという課題意識を、そもそも持っていないということなのか……???
一方で、去年1秒もやらなかったこと、例えば「もう少し将棋に時間をかけて取り組んでいれば……」というふうに考えたことはないな。別に将棋のことは何とも思っていないので。

音ゲーDDR)が分かりやすくて、去年はDDRに集中していたのでDDRの腕前は伸びたけど、その代償に他の音ゲードラマニとサンボル)は腕が落ちた。なぜならプレイしていないので。
それでもDDRのプレイ頻度はそんなに高くないから、もっとプレイ回数を増やせばもっとうまくなるだろうけど、そうすると他の活動の時間を減らさなければならない。
まぁ労力を投下したからと言って結果が必ず良くなるとは限らないな。 極端な場合、時間と労力をかけたのに何の成果も得られなかった場合もあるから……(遠い目)

……と言いつつ、ダラダラとゲームしてたら休日が終わったりするんだよな。「ある活動を取って他の活動を捨てる」とかじゃなくて、「何の足しにもならない活動」に時間を割き過ぎている場合がある。
などと思っていたら、こんな動画がYouTubeのレコメンドに上がってきていた。やってみようか?Use Strategic Thinking to Create the Life You Want - YouTube

あと英語は別に「頑張って歯を食いしばって英語をやっている(=努力して英語学習に時間と労力を費やしている)」わけじゃないんだよな。
何か面白いから英単語帳を開くし、英字新聞読むし、Ankiで単語帳を進める、とかやってると、気づいたら1日30分とか1時間くらいは英語関連のことをやっている。
忙しいふりしてたけど、普通に英語学習の時間が割り込んでくる余地あるんじゃん! と自分で自分に驚いているのが1つ。
あとは、こういう風に気づいたら時間をかけて取り組んでいてスキルが上がるならそれが一番いいよね、というのが1つ。

まぁ子育てしてる人からすれば「独り身のくせに(=自分の時間と財産を全て自分のためだけに使えるくせに)何言ってるんだ」とか言われそうだが。

特に結論はない。強いて言うならば

あたりだろうか。

総評?

だいたい書きたいことは書けたので完成ということにする。

ひとまず以上です。

長文のブログを書く体力と習慣がなくなってしまった……

最近になってなぜか英語勉強の熱が再燃してきた。
英検準1級の試験に申し込みしてきた。これを期に今までの英語学習を振り返るための記事である。

小学校より前

セサミストリートとか見てたと思うけど、詳しいことはもはや覚えていない。

小学校

英語の勉強らしい勉強をしたのは、小6の最後で中学受験に合格したあとのこと。3日間か5日間だったような。
いわゆるフォニックス(英単語の発音の規則)について教わった。

中学校

Asahi Weekly

朝日新聞から発刊されている(英語学習者向けの)英字新聞。多分母親がこの新聞を取ったんだと思う。暇なときに読む、くらいだったけど、読解力の役には立ったと思う。
そういえば、思い出した。中2のときの先生が「英語の文章をノートに書き写して。写したページ数に応じて得点を与える」という課題を出したことがある。
で、俺はこのAsahi Weeklyの記事の文章を書き写して提出した。ノートが返却されたときに「君は難しい文章を書いてくれたので、通常の得点の2倍で計算しておきます」という注釈が書いてあって、嬉しかったな。
むしろ、英字新聞のような書き写すネタが無かった他の人は、何の文章を書いていたんだろうか? 教科書や問題集の文面を書き写すだけだと、飽きると思うけど。

K会

河合塾系列の塾。夏期講習などの長期休みだけ参加。
「英語を学ぶんじゃなくて、英語で学ぶんや!」みたいなコンセプトで、聖書とかギリシャ神話とか、あとはイギリスの歴史とか科学史とか、色々な長文を読まされた気がする。何ページも続く英文に抵抗がなくなったのはK会のおかげかもしれない。

PEANUTS / スヌーピー

小学校の頃からスヌーピーの漫画が好きでよく読んでいたが、中学校に上がって英語で読めるようになると日本語訳と見比べながら読むようになったかな。
海外の文学作品とかだったら、日本語版には日本語だけが書いてあるけど、PEANUTSはなぜか昔から原文と和訳を併記する形だった。
(あの独特の書き文字も漫画作品の一部だからかなぁ。日本語版の形式にもよるが、セリフの中の英文はそのままで、枠の外に日本語訳が書いてあることが多い。)
偶然だけど、好きになった作品が英語の勉強に役立つもので良かった。

高校

システム英単語

大学受験のときの英単語帳はシステム英単語の1冊だけだった。単語はフレーズで覚えると良い、という序文に感銘を受けたことを覚えている。(最新版のシステム英単語だと序文は違っている。当時の版のもの。)
この教えに従い、現在に至るまで、単語はフレーズで覚えたい派である。
しかしフレーズで覚えられるという条件を満たす、システム英単語より上のレベルの単語帳を見つけていない……
誰か「フレーズで覚えるという、システム英単語と同形式の単語帳をまとめてレビュー」って記事書いてくれないかな。

受験当時に使っていたのはこの版。 https://www.amazon.co.jp/gp/product/4796110593/

Z会東大マスターコース

それと、塾はZ会に通っていた。この記事の中で唯一個人名が出てくるが、柳瀬晃先生である。
まぁなかなか厳しい先生だった。自作プリントを作って配ることが多く、そのプリント演習でガチで難しい構文の英文和訳を解かされた。その甲斐あって、英文解釈の力はめちゃめちゃに強くなった。自作のプリントは捨てるのも惜しいので未だに取っておいてある。
「国外留学したこともない俺が英語力で困ることもなくやっていけているのは何故か」といえば、柳瀬先生のおかげであることは間違いない。15年以上経った今でも、いくつかの話は思い出せるもんな……
「はい、『at the ... of 〜』で『〜を代償・犠牲にして』の意味になる単語4つを答えてもらおうか、では〇〇さんどうぞ」(正解は cost/price/expense/sacrifice)

柳瀬先生は「アラン・ド・ボトン」という小説家が好きらしく、この人の文章から自作プリントを作ることが非常に多い。そういや、この「アラン・ド・ボトン」の小説をいつか読もうと思って、まだ読んでないな……

(柳瀬先生の評判を知りたくてこの記事を読む東大受験生はいないと思うが、もしいたら1点だけ注意。英作文については柳瀬先生は全部カットするので、自分でちゃんと対策しましょう。自分でやらなかった俺は試験本番で英作文の2問中1問を空欄で提出してしまった。まぁ、それでも受かったけど。)

大学〜大学院

TOEIC(2007年?月)

1回公式問題集を解いただけで受験。確か920点。
すっかり拍子抜けした俺は「はーん、TOEICなんて簡単じゃねーか」という気持ちになった。

TOEFL(2010年5月)

大学院の入試で、英語の試験の代わりにTOEFL iBTのスコアを提出する必要があったので受けた。
これは手元に記録がある。 120点満点で、Reading 27, Listening 22, Speaking 14, Writing 20で83点。 Reading > Listening > Writing > Speaking という大小関係なのは納得である。Speakingは普段全然やってないから、できる気がしないからな。

TOEIC(2011年11月)

M1の11月なので、就活のためにもう一度受けとくかと思って受けたんだろう。 これは手元に記録がある。総合975(Listening 495, Reading 480)。 絶対リーディングのほうが自信があったのに、リスニングが満点でリーディングが満点-15というのが意外。

社会人

TOEIC (2013年4月)

新卒で入った会社で、業務後に受けさせられたやつ。なので団体試験。(TOEIC IPテスト) 確か905点。

Anki

単語帳アプリ。出題と復習までの間隔をうまく調整してくれる。PCとスマホで同期が取れる。そういう特徴についてはここで説明する気はないので他を見てほしいが、オススメのアプリである。
Ankiに入れた単語帳は英語だけじゃなくて、資格試験(応用情報/LPIC/セキュスペ)で覚えたいものとか、その他IT関連で覚えておきたいものを適宜入れていた。前職では社員食堂があったので、そこで並ぶときか昼飯を食べながらでAnkiをやることが多かった。
ただ、何年かログインしていないとデータが消える。最近(2023年12月)にAnkiを再度使おうと思って、ログインしたら消えていた。

Ankiを使う場合、自分で問題を作るか、既存の(公開されている)デッキを使うかのどちらかになる。 資格試験の途中などで、覚えたいものが出てきた場合は当然前者だが、ここで大きな欠点がある。自分で問題を作って入力するのがめちゃめちゃ時間がかかるのだ。 休日にカードを作り始めたら1〜2時間経過していたことも結構あったような覚えがある。

単語のリストがあって(たとえばSVLとか)、1000個の単語とその訳を覚えたい、とかであればある程度自動的にできるかもしれない。……が、俺は普段、覚えたい単語に出会ったときは以下のステップを踏む。

なので時間もかかるし自動化もし辛い。悩ましいところである。

語彙力高めの日-英のデッキは少ないので、 SATとかGRE向けの、英英の(日本語訳の無い)デッキを使ってた。 ……という話が旧ブログに書いてあるな。

http://luvtome.blog5.fc2.com/blog-entry-593.html
http://luvtome.blog5.fc2.com/blog-entry-600.html
http://luvtome.blog5.fc2.com/blog-entry-613.html

TestYourVocab

2023年12月23日の結果

(2023年12月23日時点) 書くついでにまた測ったら9201と言われた。 10000まで行きたい〜と思いつつ、あと800覚えるのなかなか大変だなぁ。うへ。

2024年9月25日の結果

(2024年9月25日時点)

1冊の単語帳を1127日かけて2周したら語彙力が1万2千語になった: わたしが知らないスゴ本は、きっとあなたが読んでいる を読んだついでに測った。

9201→12672 3471も増えてる!?
いやー、前回からの差分って

全部で1600語くらいだと思うけど。ちょっと確認のために調べながらやったりしたので(少し考えて意味を出せたものはチェックを付けた)結構高めの結果になったんだと思う。

英字新聞

ProPublicaのTwitterアカウントをフォローしている。何のきっかけで知ったのか忘れたけど。 ProPublica、New York Timesはたまに読んでた。……久々にNew York Timesを再訪問したら、有料会員にならないと全然記事が読めなくなっていてガッカリした。 最近はGuardianが無料で全部読めるということに気づいたので、たまに読んでいる。


何か書き加えることができたら加筆します。
それでは。

IIJmioの料金プランを2023年6月18日に、昔のライトスタートプラン(データ6GB)→新しいギガプラン(データ5GB)に変更したという話。
請求額は税込み2700円〜3000円から1000円程度に減った。もっと早くに変えておけばよかったぜ……!

プラン変更前の状況

マイページから確認すると下記のとおりだった。

申し込み日:2016年10月30日
料金プラン:ライトスタートプラン

毎月の料金は、
月額基本料(ライトスタートプラン) 税抜1520円、税込1672円
音声通話機能付帯料 税抜700円、税込770円
で合計税込2442円。
これに通話代が数百円かかって、月に2700円〜3000円くらい払っている。

倉田けい さんのIIJmioの広告がTwitterで流れてきて、俺は重い腰を上げたのであった。

IIJmio (@iijmio) May 9, 2023

変更先のギガプランを決める

ライトスタートプランは、データが毎月6GB使えるプランである。
https://www.itmedia.co.jp/mobile/articles/2302/22/news203.html によれば、 4GBのプランが2023年2月に5GBに増量された。 ライトスタートプランの6GBから移行するなら、これが良いだろう。

過去30日間のデータ利用量を確認したら(正直ここはもっと前までデータ利用量を確認させてほしい)、ちょうど6GBちょっとだった。
ただこれは1泊旅行に行ったときに2GB以上使っている影響が大きい。普段は4GBでギリギリ、5GBなら余裕を持って使える、というところか。5GBに上がってくれて助かった。

と思ったらどうやらギガプランの場合は月ごとのデータ利用量を照会できるらしい。
https://help.iijmio.jp/answer/611e5819ae05cd001c0ec994?search=true
プラン切替後に確認したが、 ギガプランを使っている時期だけでなく旧プランのときも含めて 過去1年分の月ごとのデータ利用量を確認できる。

(余談:itmediaの記事中の資料によればギガプランの人が9割近くいるらしい。俺完全にラガードじゃん。)

プランの「データを分け合う方式」の違い

https://www.iijmio.jp/hdc/spec/index.html#comparison の比較にある通りで、データを分け合う方式が異なる。

新しいギガプラン = プラン”間”でデータを分け合う
例)2ギガプラン、5ギガプランをそれぞれ契約。合計7ギガを2人で分け合う。
従来のライトスタートプランなど = プラン”内”でデータを分け合う
例)ファミリーシェアプラン(12GB)内でSIMカード3枚でデータ量を分け合う。
https://www.iijmio.jp/hdc/spec/index.html#comparison

俺のiPad Proは、SIMを差せるのに差さずに使い続けるという変な使い方をしている。
ギガプランの場合、もしiPadにSIMを入れたくなったら、新しく別のギガプランを契約する必要がある。SIMが2枚必要ならば契約プランも2つ必要だからだ。
この点がずっと引っかかっていて躊躇っていたけど、いまはSIM無しで使っているので、SIMを入れたくなったら考えることにしよう……。

プラン変更時の注意点

重要説明事項からコピー。

IIJmioモバイルサービス(ギガプラン)へ変更された場合、元のプラン(IIJmioモバイルサービスまたはIIJmioモバイルプラスサービス)へは戻すことはできません。
IIJmioモバイルサービスまたはIIJmioモバイルプラスサービスでご利用中のクーポン残量を引き継ぐことはできません。残量は消滅します。

ずっと溜まっていたデータ通信量の残量は消えてしまうので注意。

プラン変更後の状況

6月中旬に変更したので、7月1日から適用になった。

データについて。上記の通り、変更のタイミングでクーポン残量が無くなったが、7月は5GBのうち4.7GBを使った。この調子なら少しずつ溜まるので大丈夫だろう。

料金について。 まずギガプランの5GBは税抜900円、税込990円だ。
2023年7月利用分の請求金額には、6月の電話の通話料が入る。(電話の通話料に関しては1ヶ月ズレるらしい)6月はたまたま電話を使わなかったので、合計の支払い料は1000円を切った。 マジか……ネットで完結する手続きするだけで月間1700円くらい浮いたぞ……もっと早くに変えておけばよかったぜ……!

それでは。

daskでデータ絞り込みをするためにquery関数を使ったけど、構文が難しくてちょっと詰まった話。

daskのDataFrameに対するquery関数の公式ドキュメントはこちらだ。
dask.dataframe.DataFrame.query

このドキュメントを見ると、
「pandasは@で変数名を使えるが、daskではそれは使えないので、代わりにf文字列かlocal_dictキーワードを使ってくれ」と書いてある。
なるほど、@の代わりにf文字列を使えばそれで良いのね。……と単純に考えていると、ちょっとつまずく。という話である。

準備

import pandas as pd import dask.dataframe as dd import dask pd.options.display.notebook_repr_html = False

print(pd.version) print(dask.version)

1.1.2 2023.1.0

サンプルデータの作成

df = pd.DataFrame({ 'name' : ['Alice', 'Bob', 'Charlie', 'Charlie', 'Alice', 'Bob'], 'item' : ['aaa', 'bbb', 'ccc', 'ddd', 'eee', 'fff'], 'number' : [3, 2, 4, 3, 2, 1], 'id_code' : ['012', '123', '234', '123', '012', '345'] })

df

  name item  number id_code

0 Alice aaa 3 012 1 Bob bbb 2 123 2 Charlie ccc 4 234 3 Charlie ddd 3 123 4 Alice eee 2 012 5 Bob fff 1 345

df.dtypes

name object item object number int64 id_code object dtype: object

ddf = dd.from_pandas(df)


ValueError                                Traceback (most recent call last)
<ipython-input-6-bcb6b963da62> in <module>
----> 1 ddf = dd.from_pandas(df)

/usr/local/lib/python3.8/site-packages/dask/dataframe/io/io.py in from_pandas(data, npartitions, chunksize, sort, name)
    260 
    261     if (npartitions is None) == (chunksize is None):
--> 262         raise ValueError("Exactly one of npartitions and chunksize must be specified.")
    263 
    264     nrows = len(data)
ValueError: Exactly one of npartitions and chunksize must be specified.

どうもよく分かっていないのだが、dask.dataframe.from_pandasはnpartitionsとchunksizeのうちどちらか片方(のみ)を指定する必要があるらしい。 今は特に何でも良いので、npartitions=1を指定する。

ddf = dd.from_pandas(df, npartitions=1) print(ddf)

Dask DataFrame Structure: name item number id_code npartitions=1
0 object object int64 object 5 ... ... ... ... Dask Name: from_pandas, 1 graph layer

ddf.compute()

  name item  number id_code

0 Alice aaa 3 012 1 Bob bbb 2 123 2 Charlie ccc 4 234 3 Charlie ddd 3 123 4 Alice eee 2 012 5 Bob fff 1 345

これで準備はできた。

数値型のカラムの場合

上述の公式ドキュメントにも載っている、数字の例を見てみよう。
まず、データのうち、numberカラムが2であるものを抽出しよう。

df.query("number==2")

name item  number id_code

1 Bob bbb 2 123 4 Alice eee 2 012

ddf.query("number==2").compute()

name item  number id_code

1 Bob bbb 2 123 4 Alice eee 2 012

num = 2 df.query(f"number==@num")

name item  number id_code

1 Bob bbb 2 123 4 Alice eee 2 012

num = 2 ddf.query(f"number=={num}").compute()

name item  number id_code

1 Bob bbb 2 123 4 Alice eee 2 012

num = 2 df.query(f"number=={num}")

name item  number id_code

1 Bob bbb 2 123 4 Alice eee 2 012

pandas側で@variable_nameと書く代わりに、daskでは{variable_name}と書けば良さそうな気がしてくる。 ところがそれが上手く行かないケースが存在するのだ。

文字列型のカラムの場合

データのうち、nameカラムが"Bob"であるものを抽出しよう。

df.query("name=='Bob'")

name item number id_code 1 Bob bbb 2 123 5 Bob fff 1 345

ddf.query("name=='Bob'").compute()

name item number id_code 1 Bob bbb 2 123 5 Bob fff 1 345

ここまでは何も問題ない。
ところが、変数名を使用すると状況が変わってくる。

target = 'Bob' df.query(f"name==@target")

name item number id_code 1 Bob bbb 2 123 5 Bob fff 1 345

pandas側で@variable_nameと書く代わりに、daskでは{variable_name}と書くと失敗する。

target = 'Bob' ddf.query(f"name=={target}").compute()

エラー。長いので折りたたみます。

クリックでエラー内容を表示

KeyError                                  Traceback (most recent call last)
/usr/local/lib/python3.8/site-packages/pandas/core/computation/scope.py in resolve(self, key, is_local)
    187             if self.has_resolvers:
--> 188                 return self.resolvers[key]
    189 
/usr/local/Cellar/python@3.8/3.8.5/Frameworks/Python.framework/Versions/3.8/lib/python3.8/collections/__init__.py in __getitem__(self, key)
    897                 pass
--> 898         return self.__missing__(key)            # support subclasses that define __missing__
    899 
/usr/local/Cellar/python@3.8/3.8.5/Frameworks/Python.framework/Versions/3.8/lib/python3.8/collections/__init__.py in __missing__(self, key)
    889     def __missing__(self, key):
--> 890         raise KeyError(key)
    891 
KeyError: 'Bob'

During handling of the above exception, another exception occurred:
KeyError                                  Traceback (most recent call last)
/usr/local/lib/python3.8/site-packages/pandas/core/computation/scope.py in resolve(self, key, is_local)
    198                 # e.g., df[df > 0]
--> 199                 return self.temps[key]
    200             except KeyError as err:
KeyError: 'Bob'

The above exception was the direct cause of the following exception:
UndefinedVariableError                    Traceback (most recent call last)
/usr/local/lib/python3.8/site-packages/dask/dataframe/utils.py in raise_on_meta_error(funcname, udf)
    194     try:
--> 195         yield
    196     except Exception as e:
/usr/local/lib/python3.8/site-packages/dask/dataframe/core.py in _emulate(func, udf, *args, **kwargs)
   6570     with raise_on_meta_error(funcname(func), udf=udf), check_numeric_only_deprecation():
-> 6571         return func(*_extract_meta(args, True), **_extract_meta(kwargs, True))
   6572 
/usr/local/lib/python3.8/site-packages/dask/utils.py in __call__(self, _methodcaller__obj, *args, **kwargs)
   1102     def __call__(self, __obj, *args, **kwargs):
-> 1103         return getattr(__obj, self.method)(*args, **kwargs)
   1104 
/usr/local/lib/python3.8/site-packages/pandas/core/frame.py in query(self, expr, inplace, **kwargs)
   3339         kwargs["target"] = None
-> 3340         res = self.eval(expr, **kwargs)
   3341 
/usr/local/lib/python3.8/site-packages/pandas/core/frame.py in eval(self, expr, inplace, **kwargs)
   3469 
-> 3470         return _eval(expr, inplace=inplace, **kwargs)
   3471 
/usr/local/lib/python3.8/site-packages/pandas/core/computation/eval.py in eval(expr, parser, engine, truediv, local_dict, global_dict, resolvers, level, target, inplace)
    340 
--> 341         parsed_expr = Expr(expr, engine=engine, parser=parser, env=env)
    342 
/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py in __init__(self, expr, engine, parser, env, level)
    786         self._visitor = _parsers[parser](self.env, self.engine, self.parser)
--> 787         self.terms = self.parse()
    788 
/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py in parse(self)
    805         """
--> 806         return self._visitor.visit(self.expr)
    807 
/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py in visit(self, node, **kwargs)
    397         visitor = getattr(self, method)
--> 398         return visitor(node, **kwargs)
    399 
/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py in visit_Module(self, node, **kwargs)
    403         expr = node.body[0]
--> 404         return self.visit(expr, **kwargs)
    405 
/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py in visit(self, node, **kwargs)
    397         visitor = getattr(self, method)
--> 398         return visitor(node, **kwargs)
    399 
/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py in visit_Expr(self, node, **kwargs)
    406     def visit_Expr(self, node, **kwargs):
--> 407         return self.visit(node.value, **kwargs)
    408 
/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py in visit(self, node, **kwargs)
    397         visitor = getattr(self, method)
--> 398         return visitor(node, **kwargs)
    399 
/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py in visit_Compare(self, node, **kwargs)
    698             binop = ast.BinOp(op=op, left=node.left, right=comps[0])
--> 699             return self.visit(binop)
    700 
/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py in visit(self, node, **kwargs)
    397         visitor = getattr(self, method)
--> 398         return visitor(node, **kwargs)
    399 
/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py in visit_BinOp(self, node, **kwargs)
    519     def visit_BinOp(self, node, **kwargs):
--> 520         op, op_class, left, right = self._maybe_transform_eq_ne(node)
    521         left, right = self._maybe_downcast_constants(left, right)
/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py in _maybe_transform_eq_ne(self, node, left, right)
    440         if right is None:
--> 441             right = self.visit(node.right, side="right")
    442         op, op_class, left, right = self._rewrite_membership_op(node, left, right)
/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py in visit(self, node, **kwargs)
    397         visitor = getattr(self, method)
--> 398         return visitor(node, **kwargs)
    399 
/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py in visit_Name(self, node, **kwargs)
    532     def visit_Name(self, node, **kwargs):
--> 533         return self.term_type(node.id, self.env, **kwargs)
    534 
/usr/local/lib/python3.8/site-packages/pandas/core/computation/ops.py in __init__(self, name, env, side, encoding)
     83         self.is_local = tname.startswith(_LOCAL_TAG) or tname in _DEFAULT_GLOBALS
---> 84         self._value = self._resolve_name()
     85         self.encoding = encoding
/usr/local/lib/python3.8/site-packages/pandas/core/computation/ops.py in _resolve_name(self)
    100     def _resolve_name(self):
--> 101         res = self.env.resolve(self.local_name, is_local=self.is_local)
    102         self.update(res)
/usr/local/lib/python3.8/site-packages/pandas/core/computation/scope.py in resolve(self, key, is_local)
    203 
--> 204                 raise UndefinedVariableError(key, is_local) from err
    205 
UndefinedVariableError: name 'Bob' is not defined

The above exception was the direct cause of the following exception:
ValueError                                Traceback (most recent call last)
<ipython-input-17-6b73727f207c> in <module>
      1 # dask 変数名を使用 f文字列 失敗例
      2 target = 'Bob'
----> 3 ddf.query(f"name=={target}").compute()

/usr/local/lib/python3.8/site-packages/dask/dataframe/core.py in query(self, expr, **kwargs)
   5178         2  1  3    2
   5179         """
-> 5180         return self.map_partitions(M.query, expr, **kwargs)
   5181 
   5182     @derived_from(pd.DataFrame)
/usr/local/lib/python3.8/site-packages/dask/dataframe/core.py in map_partitions(self, func, *args, **kwargs)
    873         None as the division.
    874         """
--> 875         return map_partitions(func, self, *args, **kwargs)
    876 
    877     @insert_meta_param_description(pad=12)
/usr/local/lib/python3.8/site-packages/dask/dataframe/core.py in map_partitions(func, meta, enforce_metadata, transform_divisions, align_dataframes, *args, **kwargs)
   6639     dfs = [df for df in args if isinstance(df, _Frame)]
   6640 
-> 6641     meta = _get_meta_map_partitions(args, dfs, func, kwargs, meta, parent_meta)
   6642     if all(isinstance(arg, Scalar) for arg in args):
   6643         layer = {
/usr/local/lib/python3.8/site-packages/dask/dataframe/core.py in _get_meta_map_partitions(args, dfs, func, kwargs, meta, parent_meta)
   6750         # Use non-normalized kwargs here, as we want the real values (not
   6751         # delayed values)
-> 6752         meta = _emulate(func, *args, udf=True, **kwargs)
   6753         meta_is_emulated = True
   6754     else:
/usr/local/lib/python3.8/site-packages/dask/dataframe/core.py in _emulate(func, udf, *args, **kwargs)
   6569     """
   6570     with raise_on_meta_error(funcname(func), udf=udf), check_numeric_only_deprecation():
-> 6571         return func(*_extract_meta(args, True), **_extract_meta(kwargs, True))
   6572 
   6573 
/usr/local/Cellar/python@3.8/3.8.5/Frameworks/Python.framework/Versions/3.8/lib/python3.8/contextlib.py in __exit__(self, type, value, traceback)
    129                 value = type()
    130             try:
--> 131                 self.gen.throw(type, value, traceback)
    132             except StopIteration as exc:
    133                 # Suppress StopIteration *unless* it's the same exception that
/usr/local/lib/python3.8/site-packages/dask/dataframe/utils.py in raise_on_meta_error(funcname, udf)
    214         )
    215         msg = msg.format(f" in `{funcname}`" if funcname else "", repr(e), tb)
--> 216         raise ValueError(msg) from e
    217 
    218 
ValueError: Metadata inference failed in `query`.

You have supplied a custom function and Dask is unable to 
determine the type of output that that function returns. 

To resolve this please provide a meta= keyword.
The docstring of the Dask function you ran should have more information.

Original error is below:
------------------------
UndefinedVariableError("name 'Bob' is not defined")

Traceback:
---------
  File "/usr/local/lib/python3.8/site-packages/dask/dataframe/utils.py", line 195, in raise_on_meta_error
    yield
  File "/usr/local/lib/python3.8/site-packages/dask/dataframe/core.py", line 6571, in _emulate
    return func(*_extract_meta(args, True), **_extract_meta(kwargs, True))
  File "/usr/local/lib/python3.8/site-packages/dask/utils.py", line 1103, in __call__
    return getattr(__obj, self.method)(*args, **kwargs)
  File "/usr/local/lib/python3.8/site-packages/pandas/core/frame.py", line 3340, in query
    res = self.eval(expr, **kwargs)
  File "/usr/local/lib/python3.8/site-packages/pandas/core/frame.py", line 3470, in eval
    return _eval(expr, inplace=inplace, **kwargs)
  File "/usr/local/lib/python3.8/site-packages/pandas/core/computation/eval.py", line 341, in eval
    parsed_expr = Expr(expr, engine=engine, parser=parser, env=env)
  File "/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py", line 787, in __init__
    self.terms = self.parse()
  File "/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py", line 806, in parse
    return self._visitor.visit(self.expr)
  File "/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py", line 398, in visit
    return visitor(node, **kwargs)
  File "/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py", line 404, in visit_Module
    return self.visit(expr, **kwargs)
  File "/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py", line 398, in visit
    return visitor(node, **kwargs)
  File "/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py", line 407, in visit_Expr
    return self.visit(node.value, **kwargs)
  File "/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py", line 398, in visit
    return visitor(node, **kwargs)
  File "/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py", line 699, in visit_Compare
    return self.visit(binop)
  File "/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py", line 398, in visit
    return visitor(node, **kwargs)
  File "/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py", line 520, in visit_BinOp
    op, op_class, left, right = self._maybe_transform_eq_ne(node)
  File "/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py", line 441, in _maybe_transform_eq_ne
    right = self.visit(node.right, side="right")
  File "/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py", line 398, in visit
    return visitor(node, **kwargs)
  File "/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py", line 533, in visit_Name
    return self.term_type(node.id, self.env, **kwargs)
  File "/usr/local/lib/python3.8/site-packages/pandas/core/computation/ops.py", line 84, in __init__
    self._value = self._resolve_name()
  File "/usr/local/lib/python3.8/site-packages/pandas/core/computation/ops.py", line 101, in _resolve_name
    res = self.env.resolve(self.local_name, is_local=self.is_local)
  File "/usr/local/lib/python3.8/site-packages/pandas/core/computation/scope.py", line 204, in resolve
    raise UndefinedVariableError(key, is_local) from err

またpandasでも似たようなエラーが出る。

target = 'Bob' df.query(f"name=={target}")

エラー。長いので折りたたみます。

クリックでエラー内容を表示

KeyError                                  Traceback (most recent call last)
/usr/local/lib/python3.8/site-packages/pandas/core/computation/scope.py in resolve(self, key, is_local)
    187             if self.has_resolvers:
--> 188                 return self.resolvers[key]
    189 
/usr/local/Cellar/python@3.8/3.8.5/Frameworks/Python.framework/Versions/3.8/lib/python3.8/collections/__init__.py in __getitem__(self, key)
    897                 pass
--> 898         return self.__missing__(key)            # support subclasses that define __missing__
    899 
/usr/local/Cellar/python@3.8/3.8.5/Frameworks/Python.framework/Versions/3.8/lib/python3.8/collections/__init__.py in __missing__(self, key)
    889     def __missing__(self, key):
--> 890         raise KeyError(key)
    891 
KeyError: 'Bob'

During handling of the above exception, another exception occurred:
KeyError                                  Traceback (most recent call last)
/usr/local/lib/python3.8/site-packages/pandas/core/computation/scope.py in resolve(self, key, is_local)
    198                 # e.g., df[df > 0]
--> 199                 return self.temps[key]
    200             except KeyError as err:
KeyError: 'Bob'

The above exception was the direct cause of the following exception:
UndefinedVariableError                    Traceback (most recent call last)
<ipython-input-18-52c23030a7f6> in <module>
      1 # pandas 変数名を使用 f文字列 失敗例
      2 target = 'Bob'
----> 3 df.query(f"name=={target}")

/usr/local/lib/python3.8/site-packages/pandas/core/frame.py in query(self, expr, inplace, **kwargs)
   3338         kwargs["level"] = kwargs.pop("level", 0) + 1
   3339         kwargs["target"] = None
-> 3340         res = self.eval(expr, **kwargs)
   3341 
   3342         try:
/usr/local/lib/python3.8/site-packages/pandas/core/frame.py in eval(self, expr, inplace, **kwargs)
   3468         kwargs["resolvers"] = kwargs.get("resolvers", ()) + tuple(resolvers)
   3469 
-> 3470         return _eval(expr, inplace=inplace, **kwargs)
   3471 
   3472     def select_dtypes(self, include=None, exclude=None) -> "DataFrame":
/usr/local/lib/python3.8/site-packages/pandas/core/computation/eval.py in eval(expr, parser, engine, truediv, local_dict, global_dict, resolvers, level, target, inplace)
    339         )
    340 
--> 341         parsed_expr = Expr(expr, engine=engine, parser=parser, env=env)
    342 
    343         # construct the engine and evaluate the parsed expression
/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py in __init__(self, expr, engine, parser, env, level)
    785         self.parser = parser
    786         self._visitor = _parsers[parser](self.env, self.engine, self.parser)
--> 787         self.terms = self.parse()
    788 
    789     @property
/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py in parse(self)
    804         Parse an expression.
    805         """
--> 806         return self._visitor.visit(self.expr)
    807 
    808     @property
/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py in visit(self, node, **kwargs)
    396         method = "visit_" + type(node).__name__
    397         visitor = getattr(self, method)
--> 398         return visitor(node, **kwargs)
    399 
    400     def visit_Module(self, node, **kwargs):
/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py in visit_Module(self, node, **kwargs)
    402             raise SyntaxError("only a single expression is allowed")
    403         expr = node.body[0]
--> 404         return self.visit(expr, **kwargs)
    405 
    406     def visit_Expr(self, node, **kwargs):
/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py in visit(self, node, **kwargs)
    396         method = "visit_" + type(node).__name__
    397         visitor = getattr(self, method)
--> 398         return visitor(node, **kwargs)
    399 
    400     def visit_Module(self, node, **kwargs):
/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py in visit_Expr(self, node, **kwargs)
    405 
    406     def visit_Expr(self, node, **kwargs):
--> 407         return self.visit(node.value, **kwargs)
    408 
    409     def _rewrite_membership_op(self, node, left, right):
/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py in visit(self, node, **kwargs)
    396         method = "visit_" + type(node).__name__
    397         visitor = getattr(self, method)
--> 398         return visitor(node, **kwargs)
    399 
    400     def visit_Module(self, node, **kwargs):
/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py in visit_Compare(self, node, **kwargs)
    697             op = self.translate_In(ops[0])
    698             binop = ast.BinOp(op=op, left=node.left, right=comps[0])
--> 699             return self.visit(binop)
    700 
    701         # recursive case: we have a chained comparison, a CMP b CMP c, etc.
/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py in visit(self, node, **kwargs)
    396         method = "visit_" + type(node).__name__
    397         visitor = getattr(self, method)
--> 398         return visitor(node, **kwargs)
    399 
    400     def visit_Module(self, node, **kwargs):
/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py in visit_BinOp(self, node, **kwargs)
    518 
    519     def visit_BinOp(self, node, **kwargs):
--> 520         op, op_class, left, right = self._maybe_transform_eq_ne(node)
    521         left, right = self._maybe_downcast_constants(left, right)
    522         return self._maybe_evaluate_binop(op, op_class, left, right)
/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py in _maybe_transform_eq_ne(self, node, left, right)
    439             left = self.visit(node.left, side="left")
    440         if right is None:
--> 441             right = self.visit(node.right, side="right")
    442         op, op_class, left, right = self._rewrite_membership_op(node, left, right)
    443         return op, op_class, left, right
/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py in visit(self, node, **kwargs)
    396         method = "visit_" + type(node).__name__
    397         visitor = getattr(self, method)
--> 398         return visitor(node, **kwargs)
    399 
    400     def visit_Module(self, node, **kwargs):
/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py in visit_Name(self, node, **kwargs)
    531 
    532     def visit_Name(self, node, **kwargs):
--> 533         return self.term_type(node.id, self.env, **kwargs)
    534 
    535     def visit_NameConstant(self, node, **kwargs):
/usr/local/lib/python3.8/site-packages/pandas/core/computation/ops.py in __init__(self, name, env, side, encoding)
     82         tname = str(name)
     83         self.is_local = tname.startswith(_LOCAL_TAG) or tname in _DEFAULT_GLOBALS
---> 84         self._value = self._resolve_name()
     85         self.encoding = encoding
     86 
/usr/local/lib/python3.8/site-packages/pandas/core/computation/ops.py in _resolve_name(self)
     99 
    100     def _resolve_name(self):
--> 101         res = self.env.resolve(self.local_name, is_local=self.is_local)
    102         self.update(res)
    103 
/usr/local/lib/python3.8/site-packages/pandas/core/computation/scope.py in resolve(self, key, is_local)
    202                 from pandas.core.computation.ops import UndefinedVariableError
    203 
--> 204                 raise UndefinedVariableError(key, is_local) from err
    205 
    206     def swapkey(self, old_key: str, new_key: str, new_value=None):
UndefinedVariableError: name 'Bob' is not defined

これはquery関数の中にある文字列を単独で表示させるとよく分かる。

print(f"name=={target}")

name==Bob

Bobに引用符をつけなければいけないのに、ついていない。これではBobはただの列名として扱われるはずだ。
正しい結果を得るためには、f文字列の中、Bobの外側に引用符を書く必要がある。

target = 'Bob' ddf.query(f"name=='{target}'").compute()

name item number id_code 1 Bob bbb 2 123 5 Bob fff 1 345

target = 'Bob' df.query(f"name=='{target}'")

name item number id_code 1 Bob bbb 2 123 5 Bob fff 1 345

数字が入っている文字列型の場合

データのうち、id_codeカラムが"123"であるものを抽出しよう。

df.query("id_code=='123'")

  name item  number id_code

1 Bob bbb 2 123 3 Charlie ddd 3 123

ddf.query("id_code=='123'").compute()

  name item  number id_code

1 Bob bbb 2 123 3 Charlie ddd 3 123

ここまでは何も問題ない。
ところが、変数名を使用すると状況が変わってくる。

code = '123' df.query(f"id_code==@code")

  name item  number id_code

1 Bob bbb 2 123 3 Charlie ddd 3 123

code = '123' ddf.query(f"id_code=={code}").compute()

Empty DataFrame Columns: [name, item, number, id_code] Index: []

query関数の結果は空のDataFrameになる。
エラーが出るほうがまだハッキリ間違い箇所が分かる分だけ修正しやすいかもしれない……
これもquery関数の中にある文字列を単独で表示させるとよく分かる。

print(f"id_code=={code}")

id_code==123

これをquery関数に入れると、id_codeが数字の123に等しいものを探してしまう。だから該当する行は無く、空のDataFrameが返る。
なお、pandasでは数字(より正確には10進数の整数リテラル)の先頭に0をつけてはいけないので、012で同じことをやると違う状況になる。

x = 012

File "", line 2 x = 012 ^ SyntaxError: leading zeros in decimal integer literals are not permitted; use an 0o prefix for octal integers

code = '012' ddf.query(f"id_code=={code}").compute()

エラー。長いので折りたたみます。

クリックでエラー内容を表示


SyntaxError                               Traceback (most recent call last)
/usr/local/lib/python3.8/site-packages/dask/dataframe/utils.py in raise_on_meta_error(funcname, udf)
    194     try:
--> 195         yield
    196     except Exception as e:
/usr/local/lib/python3.8/site-packages/dask/dataframe/core.py in _emulate(func, udf, *args, **kwargs)
   6570     with raise_on_meta_error(funcname(func), udf=udf), check_numeric_only_deprecation():
-> 6571         return func(*_extract_meta(args, True), **_extract_meta(kwargs, True))
   6572 
/usr/local/lib/python3.8/site-packages/dask/utils.py in __call__(self, _methodcaller__obj, *args, **kwargs)
   1102     def __call__(self, __obj, *args, **kwargs):
-> 1103         return getattr(__obj, self.method)(*args, **kwargs)
   1104 
/usr/local/lib/python3.8/site-packages/pandas/core/frame.py in query(self, expr, inplace, **kwargs)
   3339         kwargs["target"] = None
-> 3340         res = self.eval(expr, **kwargs)
   3341 
/usr/local/lib/python3.8/site-packages/pandas/core/frame.py in eval(self, expr, inplace, **kwargs)
   3469 
-> 3470         return _eval(expr, inplace=inplace, **kwargs)
   3471 
/usr/local/lib/python3.8/site-packages/pandas/core/computation/eval.py in eval(expr, parser, engine, truediv, local_dict, global_dict, resolvers, level, target, inplace)
    340 
--> 341         parsed_expr = Expr(expr, engine=engine, parser=parser, env=env)
    342 
/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py in __init__(self, expr, engine, parser, env, level)
    786         self._visitor = _parsers[parser](self.env, self.engine, self.parser)
--> 787         self.terms = self.parse()
    788 
/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py in parse(self)
    805         """
--> 806         return self._visitor.visit(self.expr)
    807 
/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py in visit(self, node, **kwargs)
    393                     e.msg = "Python keyword not valid identifier in numexpr query"
--> 394                 raise e
    395 
/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py in visit(self, node, **kwargs)
    389             try:
--> 390                 node = ast.fix_missing_locations(ast.parse(clean))
    391             except SyntaxError as e:
/usr/local/Cellar/python@3.8/3.8.5/Frameworks/Python.framework/Versions/3.8/lib/python3.8/ast.py in parse(source, filename, mode, type_comments, feature_version)
     46     # Else it should be an int giving the minor version for 3.x.
---> 47     return compile(source, filename, mode, flags,
     48                    _feature_version=feature_version)
SyntaxError: invalid syntax (<unknown>, line 1)

The above exception was the direct cause of the following exception:
ValueError                                Traceback (most recent call last)
<ipython-input-28-5004d24aebb2> in <module>
      1 # dask 変数名を使用 f文字列 失敗例その2
      2 code = '012'
----> 3 ddf.query(f"id_code=={code}").compute()

/usr/local/lib/python3.8/site-packages/dask/dataframe/core.py in query(self, expr, **kwargs)
   5178         2  1  3    2
   5179         """
-> 5180         return self.map_partitions(M.query, expr, **kwargs)
   5181 
   5182     @derived_from(pd.DataFrame)
/usr/local/lib/python3.8/site-packages/dask/dataframe/core.py in map_partitions(self, func, *args, **kwargs)
    873         None as the division.
    874         """
--> 875         return map_partitions(func, self, *args, **kwargs)
    876 
    877     @insert_meta_param_description(pad=12)
/usr/local/lib/python3.8/site-packages/dask/dataframe/core.py in map_partitions(func, meta, enforce_metadata, transform_divisions, align_dataframes, *args, **kwargs)
   6639     dfs = [df for df in args if isinstance(df, _Frame)]
   6640 
-> 6641     meta = _get_meta_map_partitions(args, dfs, func, kwargs, meta, parent_meta)
   6642     if all(isinstance(arg, Scalar) for arg in args):
   6643         layer = {
/usr/local/lib/python3.8/site-packages/dask/dataframe/core.py in _get_meta_map_partitions(args, dfs, func, kwargs, meta, parent_meta)
   6750         # Use non-normalized kwargs here, as we want the real values (not
   6751         # delayed values)
-> 6752         meta = _emulate(func, *args, udf=True, **kwargs)
   6753         meta_is_emulated = True
   6754     else:
/usr/local/lib/python3.8/site-packages/dask/dataframe/core.py in _emulate(func, udf, *args, **kwargs)
   6569     """
   6570     with raise_on_meta_error(funcname(func), udf=udf), check_numeric_only_deprecation():
-> 6571         return func(*_extract_meta(args, True), **_extract_meta(kwargs, True))
   6572 
   6573 
/usr/local/Cellar/python@3.8/3.8.5/Frameworks/Python.framework/Versions/3.8/lib/python3.8/contextlib.py in __exit__(self, type, value, traceback)
    129                 value = type()
    130             try:
--> 131                 self.gen.throw(type, value, traceback)
    132             except StopIteration as exc:
    133                 # Suppress StopIteration *unless* it's the same exception that
/usr/local/lib/python3.8/site-packages/dask/dataframe/utils.py in raise_on_meta_error(funcname, udf)
    214         )
    215         msg = msg.format(f" in `{funcname}`" if funcname else "", repr(e), tb)
--> 216         raise ValueError(msg) from e
    217 
    218 
ValueError: Metadata inference failed in `query`.

You have supplied a custom function and Dask is unable to 
determine the type of output that that function returns. 

To resolve this please provide a meta= keyword.
The docstring of the Dask function you ran should have more information.

Original error is below:
------------------------
SyntaxError('invalid syntax', ('<unknown>', 1, 13, 'id_code ==0 12 \n'))

Traceback:
---------
  File "/usr/local/lib/python3.8/site-packages/dask/dataframe/utils.py", line 195, in raise_on_meta_error
    yield
  File "/usr/local/lib/python3.8/site-packages/dask/dataframe/core.py", line 6571, in _emulate
    return func(*_extract_meta(args, True), **_extract_meta(kwargs, True))
  File "/usr/local/lib/python3.8/site-packages/dask/utils.py", line 1103, in __call__
    return getattr(__obj, self.method)(*args, **kwargs)
  File "/usr/local/lib/python3.8/site-packages/pandas/core/frame.py", line 3340, in query
    res = self.eval(expr, **kwargs)
  File "/usr/local/lib/python3.8/site-packages/pandas/core/frame.py", line 3470, in eval
    return _eval(expr, inplace=inplace, **kwargs)
  File "/usr/local/lib/python3.8/site-packages/pandas/core/computation/eval.py", line 341, in eval
    parsed_expr = Expr(expr, engine=engine, parser=parser, env=env)
  File "/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py", line 787, in __init__
    self.terms = self.parse()
  File "/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py", line 806, in parse
    return self._visitor.visit(self.expr)
  File "/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py", line 394, in visit
    raise e
  File "/usr/local/lib/python3.8/site-packages/pandas/core/computation/expr.py", line 390, in visit
    node = ast.fix_missing_locations(ast.parse(clean))
  File "/usr/local/Cellar/python@3.8/3.8.5/Frameworks/Python.framework/Versions/3.8/lib/python3.8/ast.py", line 47, in parse
    return compile(source, filename, mode, flags,

正しい結果を得るためには、f文字列の中、123の外側に引用符を書く必要がある。

code = '123' ddf.query(f"id_code=='{code}'").compute()

  name item  number id_code

1 Bob bbb 2 123 3 Charlie ddd 3 123

まとめ

「pandas側で@variable_nameと書く代わりに、daskでは{variable_name}と書く」という意識だと失敗する。
「変数を使わずにquery関数の引数の文字列を書くにはどうすればよいか」「それをf文字列で実現するにはどうすればよいか」 を考えれば良さそう。長々と色々な例を書いてきたけど、要約すれば上記のとおりになる。
daskのqueryの場合、扱っているのは普通のf文字列なので、文字列内の変数を展開したときに期待通りになっていれば良いというわけだ。

……というかここまで書いて気づいたけど、 pandasのqueryも引数に取るのはただの文字列なんだから、

num = 2 df.query(f"number=={num}")

が行けるとか書いてたけど、ただの文字列の書き方の違いじゃん。引数の文字列をそのまま書くかf文字列の展開を使って書くかの違いじゃん。
pandasのqueryといえば@を使うのが当たり前で、f文字列でも上手くいくのが意外で特別なことのように見えてしまった。 しかし、むしろ@を使った記法の方が、引数文字列の中身が違うから特殊だった(pandasの特殊な記法)。f文字列による指定は普通のpythonが分かっていれば自然な、一般的な話であった。

それでは。