GitHub Copilot 便利ですよね。
僕は R / Tidyverse でデータ処理をする事が多いのですが、最近は Python / Rust はじめ色んな言語のチームと仕事をする機会が増えてきました。
「データ処理言語R」は全く見たことも無い、という人も少なくないのですが慣れると多分データ処理、クレンジングから初期分析には今でも最強の言語だと思います。
Rの特徴として言われることの一つに、
「コンピュータに苦労させて、人間がラクをする言語」
があります。Rに比べると、他の言語は「コンピュータにラクをさせるために、人間が苦労をする言語」なのですと。
それを証明するような、アルゴリズム的にもなんか凄いライブラリがたくさんありますが、僕みたいなへっぽこには「最小限のタイプ量で結果が得られる」事が大きなメリットだったりします。
GitHub Copilot Labs ベータへの参加権が得られましたので、Rから色んな言語に翻訳してみて「Rより短く書ける言語はあるのかな?」をざっくり調べてみました。
僕は R 以外は殆ど書いていないので、正直動くコードなのかすら不安です!
「へーそうなんだ?」くらいにだらーっと見て頂ければ幸いです。
R - dplyr (tibble)
対象としたのはこんなコードです。特になんちゃない、データ整形スクリプトです。
たまたま今書いていたスクリプトから拾っただけであまり深く考えずに選んでいます。
明らかに不要な arrange
があったりしますね。これを削除してくれたりもするかな?
id_creators <- meta_all %>%
filter(metatype == "creator") %>%
select(id, creator = meta1, num) %>%
arrange(num) %>%
group_by(id) %>%
summarise(count = n()) %>%
arrange(desc(count))
R - data.table
まずはRの別ライブラリ。今でもpolarsと並ぶ最速クラスのデータフレーム処理ライブラリです。dtply を使うと dplyr コードからほぼそのまま変換出来る上に超高速です。個人的には括弧とかが複数行を跨いでちょっと書きづらいです。
(Copilotからはライブラリの指定が出来ないのでこちらとdata.frame()だけ、chatGPTからコピペしています)
id_creators <- meta_all[metatype == "creator",
.(id = id, creator = meta1, num = num)
][order(num),
.(count = .N), by = .(id)
][order(-count)]
R - data.frame()
tibble() data.table() が生まれる前はこちらが標準でした。最近ではほぼ書かないですね。
(pythonのpandasはこちらのデータフレームを参考にしたと言われています。 )
なぜかこちらとJAVAだけコメントが付きました。文化的な違い?
# Filter
meta_filtered <- meta_all[meta_all$metatype == "creator",]
# Select
meta_selected <- meta_filtered[, c("id", "meta1", "num")]
names(meta_selected)[names(meta_selected) == "meta1"] <- "creator"
# Arrange by num
meta_arranged <- meta_selected[order(meta_selected$num),]
# Group_by and summarise
meta_grouped <- aggregate(. ~ id, meta_arranged, length)
names(meta_grouped)[names(meta_grouped) == "creator"] <- "count"
# Arrange by count (desc)
meta_final <- meta_grouped[order(-meta_grouped$count),]
# Assign result to id_creators
id_creators <- meta_final
SQL
データ処理と言えばまずSQL。dplyr は dbplyr をつかってSQL変換が可能なくらいで、難無く翻訳してくれました。 要らないarrangeも省かれてますね。
SELECT id, COUNT(*) AS count
FROM meta_all
WHERE metatype = 'creator'
GROUP BY id
ORDER BY count DESC
Python
多種多様なやり方が出てきました。最初のは polars かな? dplyrっぽい。
他にも実行するたびに違うやり方が出てきたのですが、選択肢多すぎてみんな迷子にならないのかな、って時々思います。
id_creators = meta_all.filter("metatype = 'creator'")
.select("id", "meta1 as creator", "num")
.sort("num")
.group("id")
.summarize(:count => :n)
.sort("count", :desc)
id_creators = meta_all[meta_all.metatype == 'creator'][['id', 'meta1', 'num']] \
.assign(creator=lambda x: x.meta1) \
.sort_values('num') \
.groupby('id') \
.size() \
.reset_index(name='count') \
.sort_values('count', ascending=False)
id_creators = meta_all[
meta_all["metatype"] == "creator", ["id", "meta1", "num"]
].rename(columns={"meta1": "creator", "num": "count"}).groupby(
"id").agg({"count": "count"}).sort_values("count", ascending=False)
Julia
最近のデータ処理ならJulia! って思ってたのとだいぶ違う感じで出てきました。Rの data.table っぽい?
meta_all = DataFrame(id = rand(1:10, 100), metatype = rand(["creator", "notcreator"], 100), meta1 = rand(1:10, 100), num = rand(1:10, 100))
id_creators = meta_all[meta_all.metatype .== "creator", :]
id_creators = by(id_creators, :id, df -> DataFrame(creator = df.meta1[1], num = df.num[1], count = size(df, 1)))
id_creators = sort!(id_creators, cols=[:count], rev=true)
Rust
こちらは polars ですかね。 あれ qiita ってrust認識しない? 2つ目のはなかなかわからないです。
let id_creators = meta_all
.filter(|row| row.metatype == "creator")
.select(&["id", "creator = meta1", "num"])
.arrange(&["num"])
.group_by(&["id"])
.summarise(&[("count", "n()")])
.arrange(&["desc(count)"]);
let id_creators = meta_all
.iter()
.filter(|&x| x.metatype == "creator")
.map(|x| (x.id, x.meta1, x.num))
.collect::<Vec<_>>()
.into_iter()
.sorted_by(|a, b| a.2.cmp(&b.2))
.group_by(|x| x.0)
.into_iter()
.map(|(k, v)| (k, v.count()))
.collect::<Vec<_>>()
.into_iter()
.sorted_by(|a, b| a.1.cmp(&b.1))
.collect::<Vec<_>>();
JAVA
触ってる方が多いのはこちらでしょうか。データフレーム処理ライブラリは何が人気なのだろ?こちらも色んな提案がありました。最初の奴は結構好きな感じだけど、これホントにJAVA?
なぜか JAVA だけコメントも付けてくれました。変数名がどんどん長くなっていくのも雰囲気出てるなと感じました。
d_creators = meta_all
.filter(x -> x.metatype.equals("creator"))
.select("id", "creator", "num")
.arrange("num")
.group_by("id")
.summarise("count = n()")
.arrange("desc(count)");
public class IdCreators {
public static void main(String[] args) {
// Read the data
DataFrame metaAll = DataFrame.readCsv("meta_all.csv");
// filter(metatype == "creator")
DataFrame metaAllFiltered = metaAll.filter(metaAll.col("metatype").eq("creator"));
// select(id, creator = meta1, num)
DataFrame metaAllSelected = metaAllFiltered.select(metaAllFiltered.col("id"),
metaAllFiltered.col("meta1").as("creator"), metaAllFiltered.col("num"));
// arrange(num)
DataFrame metaAllSelectedArranged = metaAllSelected.sort(metaAllSelected.col("num"));
// group_by(id)
DataFrame metaAllSelectedArrangedGrouped = metaAllSelectedArranged.groupBy(metaAllSelectedArranged.col("id"));
// summarise(count = n())
DataFrame metaAllSelectedArrangedGroupedSummarised = metaAllSelectedArrangedGrouped.count();
// arrange(desc(count))
DataFrame metaAllSelectedArrangedGroupedSummarisedArranged = metaAllSelectedArrangedGroupedSummarised
.sort(metaAllSelectedArrangedGroupedSummarised.col("count").desc());
metaAllSelectedArrangedGroupedSummarisedArranged.show();
}
}
Dart
Flutter で知名度上がってますね。そろそろ動くのか本気でわからないw
id_creators = meta_all
.where((row) => row["metatype"] == "creator")
.map((row) => {"id": row["id"], "creator": row["meta1"], "num": row["num"]})
.toList();
id_creators.sort((a, b) => a["num"].compareTo(b["num"]));
id_creators = groupBy(id_creators, (row) => row["id"])
.map((rows) => {"id": rows[0]["id"], "count": rows.length})
.toList();
id_creators.sort((a, b) => b["count"].compareTo(a["count"]));
Swift
データフレーム系のライブラリが無いと、mapを多用するのはどの言語も似ているなと思います
let id_creators = meta_all
.filter { $0.metatype == "creator" }
.map { ($0.id, $0.meta1) }
.grouped { $0.id }
.map { (id: $0, creator: $1[0].meta1, num: $1.count) }
.sorted { $0.num < $1.num }
.grouped { $0.id }
.map { (id: $0, count: $1.count) }
.sorted { $0.count > $1.count }
lua
え、まじでこれだけで動くの??
id_creators = meta_all:filter(metatype == "creator"):select(id, creator = meta1, num):arrange(num):group_by(id):summarise(count = n()):arrange(desc(count))
TypeScript
({{これで}} => ほんとに).動くの === "かなぁ..." {})) <- 書けないやつ
const id_creators = meta_all
.filter((d) => d.metatype === 'creator')
.map((d) => ({ id: d.id, creator: d.meta1, num: d.num }))
.sort((a, b) => a.num - b.num)
.reduce((acc, d) => {
if (!acc[d.id]) acc[d.id] = [];
acc[d.id].push(d.creator);
return acc;
}, {});
const id_creators2 = Object.keys(id_creators)
.sort((a, b) => id_creators[b].length - id_creators[a].length)
.map((d) => ({ id: d, count: id_creators[d].length }));
perl
いつのまにかちょっと懐かしい言語、と思うようになっていました。web初期のcgiはみんなperlでしたね。思ったよりコンパクトなコードでした
my $id_creators = $meta_all->select_cols(qw(id meta1 metatype num))
->filter(sub { $_->at('metatype') eq 'creator' })
->rename({ meta1 => 'creator' })
->arrange(sub { $_->at('num') })
->group_by(sub { $_->at('id') })
->summarise(sub { $_->at('count') = $_->nrow })
->arrange(sub { - $_->at('count') });
php
Web言語といえばこちらも。
$meta_all = $this->getMetaAll();
$metatype = 'creator';
$id_creators = $meta_all->filter(function ($row) use ($metatype) {
return $row['metatype'] === $metatype;
})->map(function ($row) {
return [
'id' => $row['id'],
'creator' => $row['meta1'],
'num' => $row['num'],
];
})->sortBy('num')->groupBy('id')->map(function ($group, $id) {
return [
'id' => $id,
'count' => count($group),
];
})->sortByDesc('count');
go
データ処理のイメージは殆どない go ですが、書けなくは無いみたい。データフレーム処理のライブラリを指定とかしたらもっと変わるのかな?
func idCreators(metaAll []Meta) []IdCreator {
var idCreators []IdCreator
for _, meta := range metaAll {
if meta.MetaType == "creator" {
var idCreator IdCreator
idCreator.Id = meta.Id
idCreator.Creator = meta.Meta1
idCreator.Num = meta.Num
idCreators = append(idCreators, idCreator)
}
}
sort.Slice(idCreators, func(i, j int) bool {
return idCreators[i].Num < idCreators[j].Num
})
var idCreatorsByCount []IdCreatorByCount
for _, idCreator := range idCreators {
var idCreatorByCount IdCreatorByCount
idCreatorByCount.Id = idCreator.Id
idCreatorByCount.Creator = idCreator.Creator
idCreatorByCount.Num = idCreator.Num
idCreatorByCount.Count = 1
idCreatorsByCount = append(idCreatorsByCount, idCreatorByCount)
}
var idCreatorsGroupById []IdCreatorGroupById
for _, idCreatorByCount := range idCreatorsByCount {
var idCreatorGroupById IdCreatorGroupById
idCreatorGroupById.Id = idCreatorByCount.Id
idCreatorGroupById.Creator = idCreatorByCount.Creator
idCreatorsGroupById = append(idCreatorsGroupById, idCreatorGroupById)
}
for i, idCreatorByCount := range idCreatorsByCount {
for j, idCreatorGroupById := range id
C
なぜかCへの翻訳は、まともに動かないコードや、明らかなエラーが頻繁に出ました。 プロンプト漏れてたり。
まだ多少動きそうな雰囲気があったのがこちら。 map/reduce とかCで書いた記憶殆ど無いと思ったら、メソッドチェーンなんて出来たんでしたっけ?
高級アセンブラとか言ってましたね。
var id_creators = meta_all
.filter(function(row) {
return row.metatype == "creator";
})
.map(function(row) {
return {
id: row.id,
creator: row.meta1,
num: row.num
};
})
.sort(function(a, b) {
return a.num - b.num;
})
.reduce(function(acc, row) {
if (!acc[row.id]) {
acc[row.id] = 1;
} else {
acc[row.id] += 1;
}
return acc;
}, {});
id_creators = Object.keys(id_creators).map(function(key) {
return {
id: key,
count: id_creators[key]
};
});
id_creators = id_creators.sort(function(a, b) {
return b.count - a.count;
});
c++
こちらも殆ど動作しそうなコードは出ませんでした。 お、なんか見れるコードだ?と思ったら Rcpp用のコードみたい?
[[Rcpp::export]]
DataFrame id_creators(DataFrame meta_all) {
// id_creators <- meta_all %>%
// filter(metatype == "creator") %>%
// select(id, creator = meta1, num) %>%
// arrange(num) %>%
// group_by(id) %>%
// summarise(count = n()) %>%
// arrange(desc(count))
DataFrame meta_all_filtered = meta_all[meta_all["metatype"] == "creator"];
DataFrame meta_all_selected = meta_all_filtered[{"id", "meta1", "num"}];
DataFrame meta_all_reordered = meta_all_selected.sort("num");
DataFrame meta_all_grouped = meta_all_reordered.group_by({"id"});
DataFrame meta_all_summarised = meta_all_grouped.summarise({"count"}, {"nrow"});
DataFrame meta_all_sorted = meta_all_summarised.sort({"count"}, false);
return meta_all_sorted;
}
Objective-C
こちらは力技って感じでそれっぽいコードが出てきました。スマホアプリのデータフレーム処理は何がベストなんだろう? sqliteでSQL使えって事かな?
NSMutableArray *id_creators = [[meta_all filter:^BOOL(id obj) {
NSDictionary *dict = (NSDictionary *)obj;
BOOL metatype = [[dict objectForKey:@"metatype"] isEqualToString:@"creator"];
return metatype;
}] select:^id(id obj) {
NSDictionary *dict = (NSDictionary *)obj;
NSString *id = [dict objectForKey:@"id"];
NSString *creator = [dict objectForKey:@"meta1"];
NSString *num = [dict objectForKey:@"num"];
return @{@"id": id, @"creator": creator, @"num": num};
}];
id_creators = [[id_creators arrange:^NSArray *(id obj) {
NSDictionary *dict = (NSDictionary *)obj;
NSString *num = [dict objectForKey:@"num"];
return @[num];
}] groupBy:^id(id obj) {
NSDictionary *dict = (NSDictionary *)obj;
NSString *id = [dict objectForKey:@"id"];
return id;
}];
id_creators = [[id_creators summarise:^id(id obj) {
return @{@"count": @([obj count])};
}] arrange:^NSArray *(id obj) {
NSDictionary *dict = (NSDictionary *)obj;
NSNumber *count = [dict objectForKey:@"count"];
return @[count];
}];
Cobol
みんな大好きCobolです。初めて見ました。どんな感情になれば良いのかわからずに居ます。
IDENTIFICATION DIVISION.
PROGRAM-ID. ID-CREATORS.
DATA DIVISION.
WORKING-STORAGE SECTION.
01 ID-CREATORS-RECORD.
05 ID-CREATORS-ARRAY OCCURS 100 TIMES
DEPENDING ON ID-CREATORS-COUNT.
10 ID-CREATORS-ID PIC X(20).
10 ID-CREATORS-COUNT PIC 9(5).
01 ID-CREATORS-COUNT PIC 9(5).
01 ID-CREATORS-INDEX PIC 9(5).
01 ID-CREATORS-ARRAY-INDEX PIC 9(5).
PROCEDURE DIVISION.
MOVE 0 TO ID-CREATORS-COUNT
PERFORM 100 TIMES
ADD 1 TO ID-CREATORS-INDEX
IF ID-CREATORS-INDEX > 100
GO TO END-OF-PERFORM
END-IF
PERFORM 100 TIMES
ADD 1 TO ID-CREATORS-ARRAY-INDEX
IF ID-CREATORS-ARRAY-INDEX > 100
GO TO END-OF-PERFORM
END-IF
IF ID-CREATORS-ARRAY(ID-CREATORS-ARRAY-INDEX)
NOT EQUAL TO "creator"
GO TO END-OF-PERFORM
END-IF
IF ID-CREATORS-ID(ID-CREATORS-ARRAY-INDEX)
EQUAL TO ID-CREATORS-ID(ID-CREATORS-INDEX)
ADD 1 TO ID-CREATORS-COUNT(ID-CREATORS-INDEX)
GO TO END-OF-SECOND-PERFORM
END-IF
END-PERFORM
ADD 1 TO ID-CREATORS-COUNT
MOVE ID-CREATORS-ID(ID-CREATORS-INDEX)
TO ID-CREATORS-ID(ID-CREATORS-ARRAY-INDEX)
MOVE ID-CREATORS-COUNT
TO ID-CREATORS-COUNT(ID-CREATORS-ARRAY-INDEX)
END-PERFORM
END-PERFORM
END-OF-PERFORM.
STOP RUN.
END PROGRAM ID-CREATORS.
終わりです!
僕はプログラミングの専門家でもなんでもありません!あくまで「わー Copilotって今こんな感じなのね」のプレビュー程度にお読み頂ければ幸いです。
chatGPTにコピペするより圧倒的に反応が速いのも嬉しかったです。(chatGPTって、あのぽちぽち... って喋ってる感じの演出がウマいなと思います)
それぞれの言語の専門家の方なら、もっと短く、それこそ dplyr 構文よりコンパクトに書けるのかもしれませんが、NSEにパイプといった R の機能のありがたみを再確認する時間でもありました。
僕自身はよく知らない言語をいっぱい見れて楽しかったです。 Python触る時は polars でやろ〜と思ったりもしました。
もし R をご存じない方がご覧頂けていたら、ちょっとでも便利そうと思って頂けるとお遊び冥利に尽きます。
それでは最後までお付き合い頂きましてありがとうございました!