그래프 제목(ggtitle())

김후추씨의 조언대로 그래프를 만든 신입 데이터분석가. 그런데 문제가 하나 있다. 

"그래프에 제목을 넣고 싶은데... 어떻게 해야 할까요? "

 

> ggplot(data=data_siseol,aes(x=채소구분소분류,y=생산량.톤.,fill=채소구분소분류))+geom_bar(stat="identity")+ggtitle("제주도 시설재배 야채 생산량")

이렇게요. 

 

> ggplot(data=data_siseol,aes(x=채소구분소분류,y=생산량.톤.,fill=채소구분소분류))+geom_bar(stat="identity")+ggtitle("제주도 시설재배 야채 생산량")+theme(plot.title=element_text(lineheight=1.5,face="bold"))

물론 제목 글자 크기를 키워줄 수도 있는데... 아니 글꼴 지원 안해주냐고... 이럴거면 그냥 matplotlib 쓰자

 

> ggplot(data=data_siseol,aes(x=채소구분소분류,y=생산량.톤.,fill=채소구분소분류))+geom_bar(stat="identity")+ggtitle("제주도 시설재배 야채 생산량")+theme(plot.title=element_text(lineheight=1.5,face="bold"))+coord_flip()

아까도 말했지만 cooord_flip()을 쓰면 그래프가 눕는다. 아 라벨 깔끔하다 그졍 

 

> plot+scale_x_discrete(limits=c("딸기","방울토마토"))
경고메시지(들): 
Removed 42 rows containing missing values (position_stack).

과채류만 빼고싶다고요? 예 빼세요 

 

> plot+scale_x_discrete(limits=c("깻잎","상추"),labels=c("Lettuce","penilla leaf"))
경고메시지(들): 
Removed 42 rows containing missing values (position_stack).

어? 라벨 바꼈는데?

라벨도 바꿀 수 있다. 

 

> plot+scale_x_discrete(breaks=NULL)

축 모눈이 거슬리신다고요? 아 빼드렸습니다^^ 

 

bp + theme(axis.ticks = element_blank(), axis.text.x = element_blank())

이거는 선은 냅두고 축 라벨만 빼버린다. 

 

> plot+expand_limits(y=0)

세로축 한도를 바꾸고 싶으면 이걸로 하면 되고 

 

> plot+expand_limits(y=c(0,1500,3000,4500,6000,7500,9000,10500,12000))

이걸로 수동으로 간격 멕이거나 

 

> plot+ylim(0,12000)

이걸로 시작과 끝을 정해주되 등간격으로 먹일 수도 있다. 

 

> plot+coord_cartesian(ylim=c(1500,12000))

이렇게 표시 범위를 바꿀 수도 있다.

 

> plot+scale_y_reverse()

아, 물론 뒤집는것도 된다. 

 

> sp=ggplot(dat,aes(xval,yval))+geom_point()# Setting the tick marks on an axis
# This will show tick marks on every 0.25 from 1 to 10
# The scale will show only the ones that are within range (3.50-6.25 in this case)
bp + scale_y_continuous(breaks=seq(1,10,1/4))

# The breaks can be spaced unevenly
bp + scale_y_continuous(breaks=c(4, 4.25, 4.5, 5, 6,8))

# Suppress ticks and gridlines
bp + scale_y_continuous(breaks=NULL)

# Hide tick marks and labels (on Y axis), but keep the gridlines
bp + theme(axis.ticks = element_blank(), axis.text.y = element_blank())

x축과 마찬가지로 y축도 눈금이나 라벨을 숨기는 게 된다. 

 

축 스케일이 지수 or 로그일 때 

우리의 제육쌈밥군... R로 깔끔하게 스탠다드 커브를 만들어서 냈던 게 교수님의 마음에 들었는지, 방학동안 랩에서 일해보지 않겠느냐는 제의를 받았다. 근데 왜 제육쌈밥이죠 그냥 마침 관심이 있던 분야였던 제육쌈밥군은 흔쾌히 수락했고, 첫 출근을 하게 됐는데... 실험실 선배가 그를 불러 넌지시 물어봤다. 

"교수님께 얘기는 들었어. R로 standard curve를 그렸다고... 혹시... 나 좀 도와줄 수 있어? "

제육쌈밥군이 OK하자 선배는 그래프 하나를 보여줬다. 

sorted(CLNSIG_dict.items())[n]

일본열도 아님 "교수님께서 좀 더 깔끔한 그래프를 보고 싶다고 하셨는데, 어떻게 해야 할 지 모르겠어. "
"어떻게 깔끔하게 바꾸고 싶으시대요? "
"데이터가 직선으로 보였으면 좋겠대. "

 

> library(scales)
> sp+scale_y_continuous(trans=log2_trans())

이렇게요? 

 

sp + coord_trans(y="log2")

얘는 눈금이 이렇게 바뀐다. 

"하는 김에 눈금도 바꾸면 좀 깔끔할 것 같은데... "

> sp + scale_y_continuous(trans = log2_trans(),
+                         breaks = trans_breaks("log2", function(x) 2^x),
+                         labels = trans_format("log2", math_format(2^.x)))

"제육쌈밥군! 덕분에 해결됐어! 이거 바로 논문에 넣어도 되겠대! "

 

축 비율과 축 라벨

> plot+coord_fixed(ratio=1/3)

(마른세수) 이거 꼭 비율 이렇게 해야됨? 

 

> plot+theme(axis.title.x=element_blank())

축 제목은 이렇게 빼버리면 된다. (라벨 말고 제목)

 

> plot+theme(axis.title.x=element_text(face="bold",size=18),axis.text.x=element_text(size=8))

크기도 이렇게 바꿀 수 있다. 폰트만 바꾸면 되는데 

 

> plot+scale_y_continuous(label=percent)

축 라벨이 퍼센트가 됐어요! 

 

> plot+theme(panel.grid.minor=element_blank(),panel.grid.major=element_blank())

얘는 아예 그래프의 모눈을 싹 치워버린다. 

 

> plot+theme(panel.grid.minor.y=element_blank(),panel.grid.major.y=element_blank())

위에도 썼지만 하나만 날리는것도 됨. 

 

범례

> plot+guides(fill=FALSE)
경고메시지(들): 
`guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> = "none")` instead.

상여자는 범례를 넣지 않는다!!! 

 

bp + scale_fill_discrete(breaks=c("trt1","ctrl","trt2"))

상여자는 범례 순서를 수동으로 매긴다!!! 

 

> plot+guides(fill=guide_legend(reverse=TRUE))

이거는 수동으로 매기는 게 아니라 범례 순서가 반대가 된다. 

 

> plot+guides(fill=guide_legend(title=NULL))

범례 제목은 이걸로 뺀다. 

 

bp + scale_fill_discrete(name="Experimental\nCondition",
                         breaks=c("ctrl", "trt1", "trt2"),
                         labels=c("Control", "Treatment 1", "Treatment 2"))

범례 라벨만 바꾸거나(...) 

 

# Specify both colour and shape
lp1 + scale_colour_discrete(name  ="Payer",
                            breaks=c("Female", "Male"),
                            labels=c("Woman", "Man")) +
      scale_shape_discrete(name  ="Payer",
                           breaks=c("Female", "Male"),
                           labels=c("Woman", "Man"))

데이터 바이 데이터지만 데이터 그룹별로 묶거나... 

 

> plot+theme(legend.text=element_text(colour="#939597",size=16))

범례 제목이나 내용물을 바꿀 수도 있다. 

 

> plot+theme(legend.background=element_rect(fill="gray90"),legend.position="top")

범레를 위로 치워드렸습니다^^ 

 

선이... 선이 보인다! 

넣었응게 보이지. 

 

> plot+geom_hline(aes(yintercept=100))

y절편을 설정해서 띄울 수 있다. 

 

> sp+geom_hline(aes(yintercept=0))+geom_vline(aes(xintercept=0))

근데 이건 너무 갔는데? 

 

library(dplyr)
> lines <- dat %>%
+   group_by(cond) %>%
+   summarise(
+     x = mean(xval),
+     ymin = min(yval),
+     ymax = max(yval)
+   )
sp + geom_hline(aes(yintercept=10)) +
     geom_linerange(aes(x=x, y=NULL, ymin=ymin, ymax=ymax), data=lines)

이런 것도 된다. ...저거 평균임? 

 

dat_vlines <- data.frame(cond=levels(dat$cond), xval=c(10,11.5))
dat_vlines
#>        cond xval
#> 1   control 10.0
#> 2 treatment 11.5

spf + geom_hline(aes(yintercept=10)) +
      geom_vline(aes(xintercept=xval), data=dat_vlines,
                    colour="#990000", linetype="dashed")

spf + geom_hline(aes(yintercept=10)) +
     geom_linerange(aes(x=x, y=NULL, ymin=ymin, ymax=ymax), data=lines)
#> Warning: Ignoring unknown aesthetics: y

아 나눠드리겠습니다. 

 

그것은 분할출력

이거랑 때깔까지만 보면 된다. 

 

> sp <- ggplot(tips, aes(x=total_bill, y=tip/total_bill)) + geom_point(shape=1)
> sp

내장 데이터를 활용한 그래프. 근데 이걸 좀 분할해서 띄우고 싶다... 뭐 그럴 거 아님? 

 

sp + facet_grid(sex ~ .)

가로분열(성별)

 

> sp + facet_grid(. ~ time)

세로분열(시간대)

 

> sp + facet_grid(sex ~ time)

아, 이런 것도 된다. (성별+시간대)

 

> sp + facet_grid(sex ~ time)+theme(strip.text.x=element_text(size=6),strip.text.y=element_text(size=6),strip.background=element_rect(fill="#f5df4d"))

이렇게 정보가 표시되는 부분의 디자인도 바꿀 수 있다. 

 

> labels=c(Female="Woman",Male="Man")
> sp+facet_grid(.~sex,labeller=labeller(sex=labels))

라벨이 Female이랑 Male인게 좀 거시기하면 바꾸면 된다. 

 

색깔

먹고 죽은 귀신이 때깔도 고운데 그래프는 뭐 하면 때깔이 좋아지나... 

 

> ggplot(data=data4_medium,aes(x=Product.name,y=Order))+geom_bar(stat="identity")

참고로 원래 그래프는 이렇게 단색이다. 

 

> ggplot(data=data4_medium,aes(x=Product.name,y=Order))+geom_bar(stat="identity",fill="#f7cac9")

그래서 이렇게 단색으로만 변경이 된다. 

 

> ggplot(data=data4_medium,aes(x=Product.name,y=Order,fill=Product.name))+geom_bar(stat="identity")

물론 데이터별로 먹이면 이런 화려한 그래프가 나를 감싼다. 근데 저거 파레트 못 바꾸냐고? 

 

> cbPalette <- c("#999999", "#E69F00", "#56B4E9", "#009E73", "#F0E442", "#0072B2", "#D55E00", "#CC79A7")
> ggplot(data=data4_medium,aes(x=Product.name,y=Order,fill=Product.name))+geom_bar(stat="identity")+scale_fill_manual(values=cbPalette)

되는데요? 

 

scale_colour_manual(values=cbPalette)

선이나 점이면 이거 쓰자. 

 

> ggplot(data=data4_medium,aes(x=Product.name,y=Order,fill=Product.name))+geom_bar(stat="identity")+scale_fill_hue(l=40)

Hue를 바꾸면 밝기가 달라지고(default=65)

 

> ggplot(data=data4_medium,aes(x=Product.name,y=Order,fill=Product.name))+geom_bar(stat="identity")+scale_fill_hue(c=45)

c를 바꾸면 채도가... 이건 근데 default가 얼마임? 

 

> ggplot(data=data4_medium,aes(x=Product.name,y=Order,fill=Product.name))+geom_bar(stat="identity")+scale_fill_brewer()

아, 파레트 자체를 바꾸는 것도 된다. 

참고로 말씀드리는거지만... 분량 ㄹㅇ 역대급임... 노션으로 거의 팔만대장경 나온 듯. 


데이터 관련된 얘기는 다른 글에서 다루겠습니다. 


들어가기 전에

install.packages("ggplot2")

혹시나... ggplot2가 껄려있지 않다... 

깔고 오세요... 

 

> library(ggplot2)

어디가요 깔았으면 불러여지. 

본인은 저기다가 아예 디렉토리까지 고정으로 박고 시작했다. 


막대그래프(geom_bar())

나 저 geom 자꾸 점으로 읽어... 클났음... 

아무튼! 막대그래프를 그릴 때 쓸 공공데이터는 제주도의 야채 생산 현황에 대한 공공데이터이다. 

연산 채소구분대분류 채소구분소분류 면적.ha. 생산량.톤. 조수입.백만원.
1 20-21       노지채소         월동무     5056     359575         106434
2 20-21       노지채소         양배추     1753     103222          60116
3 20-21       노지채소           당근     1357      49527          46108
4 20-21       노지채소         구마늘     1600      24427          85234
5 20-21       노지채소         잎마늘       65       2665           3212
6 20-21       노지채소      양파_조생      524      32735          32596
  데이터.기준일
1      20210809
2      20210809
3      20210809
4      20210809
5      20210809
6      20210809

이건데 한꺼번에 그래프 그리기는 힘들어서 

> data_noji=subset(data,채소구분대분류=="노지채소")
> head(data_noji)
   연산 채소구분대분류 채소구분소분류 면적.ha. 생산량.톤. 조수입.백만원.
1 20-21       노지채소         월동무     5056     359575         106434
2 20-21       노지채소         양배추     1753     103222          60116
3 20-21       노지채소           당근     1357      49527          46108
4 20-21       노지채소         구마늘     1600      24427          85234
5 20-21       노지채소         잎마늘       65       2665           3212
6 20-21       노지채소      양파_조생      524      32735          32596
  데이터.기준일
1      20210809
2      20210809
3      20210809
4      20210809
5      20210809
6      20210809
> data_siseol=subset(data,채소구분대분류=="시설채소")
> head(data_siseol)
    연산 채소구분대분류 채소구분소분류 면적.ha. 생산량.톤. 조수입.백만원.
20 20-21       시설채소           상추       12        354           1593
21 20-21       시설채소           깻잎       16        478           3537
22 20-21       시설채소           오이       19        728           1165
23 20-21       시설채소     방울토마토       32       1201           4804
24 20-21       시설채소     일반토마토       19       1451           2903
25 20-21       시설채소           딸기       39       1385          14127
   데이터.기준일
20      20210809
21      20210809
22      20210809
23      20210809
24      20210809
25      20210809

노지채소와 시설재배로 세트 나눠서 했다.

데이터 받는 곳은 나중에 알려드림... 참고로 이거말고 세개 더 받았는데 언어가 깨져서 R에서 못 불러옵디다. 인코딩 다른거는 이해하겠는데 UTF-8에서 깨지는 건 좀 심한거 아니냐...

데이터 분석 일을 하고 있는 김후추씨. (전에도 말했지만 실제로 본인 포켓몬 이름임) 어느 날 출장을 갔던 김후추씨는 보스로라가 어떻게 출장을 가지 도청에서 일하던 신입 데이터 분석가의 고충을 듣게 된다.

"채소 데이터를 분석해서 그래프로 그려오라는데, 리브레오피스가 너무 버벅거려서 그래프를 그릴 수가 없어요. 어떻게 하죠? " (리브레오피스 실제로도 버벅거림 엄청나서 본인은 csv파일도 gedit으로 연다)

 

> ggplot(data=data_siseol,aes(x=채소구분소분류,y=생산량.톤.,fill=생산량.톤.))+geom_bar(stat="identity")

뭘 고민해 R 쓰면 되지. 

근데 그래프가 이렇게 돼 있으니까 좀 거시기하네. 

 

> ggplot(data=data_siseol,aes(x=채소구분소분류,y=생산량.톤.,fill=채소구분소분류))+geom_bar(stat="identity")

한결 낫다 그죠? 

 

> ggplot(data=data_siseol,aes(x=채소구분소분류,y=생산량.톤.,fill=채소구분소분류))+geom_bar(colour="black",stat="identity")+ggtitle("제주도 시설재배 야채 생산량") + xlab("채소구분 소분류") + ylab("생산량(톤)")

......테두리는 떼버리세요. 안이뻐요. 

아, 참고로 막대그래프는 막대 포지션 옵션으로 닷지를 안 주면 누적된다. (막대가 누적된다)

 

꺾은선그래프(geom_line(), geom_point())

> data2=read.csv('bradford.csv')
> data2
  BSA.conc.mg. OD5951 OD5952 OD5953
1            0  0.001  0.000  0.000
2          200  0.250  0.255  0.241
3          400  0.359  0.400  0.354
4          600  0.550  0.600  0.601
5          800  0.770  0.780  0.755

가상의 Bradford assay 데이터이다. 이게 뭐 하는 분석인지는 나중에 알려드림. 아무튼 저걸로 꺾은선그래프를 그리려면 평균 데이터가 있어야 한다. 

 

> data2$average=round(rowMeans(data2[,c('OD5951','OD5952','OD5953')]),3)
> data2
  BSA.conc.mg. OD5951 OD5952 OD5953 average
1            0  0.001  0.000  0.000   0.000
2          200  0.250  0.255  0.241   0.249
3          400  0.359  0.400  0.354   0.371
4          600  0.550  0.600  0.601   0.584
5          800  0.770  0.780  0.755   0.768

그냥 추가했더니 소수점 개판이라 Round 줘버림... 

한참 실험수업을 듣고 있는 제육쌈밥(대짱이). 오늘의 실험은 Bradford assay였다. 조별로 Bardford assay 시약을 처리한 다음 OD595(컬럼이 좀 개판났는데 OD595가 맞음... 세 번 달아서 1, 2, 3이다.)를 측정하고 결과값을 받았다. 과제로 Standard curve를 그려오라는 과제를 받은 제육쌈밥군... 까짓거 후다닥 해치우자! 라고 생각했으나 문제가 생겼다. 

제육쌈밥군의 컴퓨터는 리눅스였고, 리브레오피스가 그날따라 매우 버벅였다는 것... 돌릴 수 있는 것은 R밖에 없는데, 이를 어쩌지? 

 

> ggplot(data=data2,aes(x=BSA.conc.mg.,y=average,group=1))+geom_line()

이렇게 하면 일단 그래프는 그려졌는데... 아 라벨 저거 뭐임? 

 

> ggplot(data=data2,aes(x=BSA.conc.mg.,y=average,group=1))+geom_line(colour="#00418c")+geom_point()+xlab("BSA concentration")+ylab("OD595")+ggtitle("Standard curve")

라벨을 바꾸고 제목 추가하고 점(...) 넣고 선을 바꿨다. 사실 저기에 R^2랑 식도 들어가는 게 맞는데 그것까지 하는 법은 나중에 알려드림. 근데 R에서 그게 되나 

 

> ggplot(data=data3,aes(x=Date,y=price,group=time,colour=time,shape=time))+geom_line()+geom_point(size=4)

꺾은선그래프는 선별로 색깔을 다르게 주는 것과 동시에 점도 다르게 줄 수 있다. 

 

> ggplot(data=data3,aes(x=Date,y=price,group=time,colour=Date,shape=time))+geom_line()+geom_point(size=4)

이건 왜 되는거지... (참고로 저 데이터 저번주 무트코인 차트임)

 

평균과 오차막대

논문에 있는 그래프를 보면 I자같이 생긴 게 있는데 그게 오차막대다. 보통은 표준편차 구해서 넣는다. (표준편차가 범위)

참고로 얘네 함수 정의해서 표준편차 구했는데 함수 없이 구할 수 있으면 그래도 된다. 여기서는 어떻게든 표준편차를 구했다는 전제 하에 진행한다. 안그러면 분량 길어져서 여러분들 스크롤하다 혈압오름... 

 

> ggplot(data=tgc,aes(x=dose,y=len,colour=supp))+geom_line()+geom_point()

이거는 단순히 그래프 그려주는 코드. 

> ggplot(data=tgc,aes(x=dose,y=len,colour=supp))+geom_line()+geom_point()+geom_errorbar(aes(ymin=len-ci,ymax=len+ci),width=.1)
# Use 95% confidence interval instead of SEM

이게 에러바 그려주는 코드다. 코드에 

aes(ymin=len-ci,ymax=len+ci)

이 부분을 len-sd, len+sd로 해 주면 표준편차로 그릴 수 있다. 

 

> ggplot(data=tgc,aes(x=dose,y=len,colour=supp))+geom_line()+geom_point()+geom_errorbar(aes(ymin=len-ci,ymax=len+ci),colour="black",width=.1)

근데 에러바는 보통 깜장이죠? 

 

> ggplot(data=tgc,aes(x=dose,y=len,colour=supp))+geom_line()+geom_point()+geom_errorbar(aes(ymin=len-se,ymax=len+se),colour="black",width=.1)+expand_limits(y=0)+scale_y_continuous(breaks=0:20*4)

y축 범위 조절하면 에러바도 줄어든다. 

막대그래프도 별로 다를 건 없다. 

> ggplot(tgc2,aes(x=dose,y=len,fill=supp))+geom_bar(stat="identity",position=position_dodge())+geom_errorbar(aes(ymin=len-se,ymax=len+se),width=.2,position=position_dodge(.9))+scale_y_continuous(breaks=0:20*4)

아, 저기서 position=dodge()를 안 주면 

막대그래프가 이렇게 된다. 이게 무슨 저세상 누적이여... 아니 이런걸 일일이 해줘야되냐고 

 

히스토그램과 밀도곡선(geom_histogram(), geom_density())

점_히스토그램 무엇... 아니 니네 약어 안쓰냐고... 

 

> set.seed(1)
> dat=data.frame(comd=factor(rep(c("A","B"),each=200)),rating=c(rnorm(200),rnorm(200,mean=.8)))

역사와 전통에 의거, 히스토그램은 랜덤 데이터 만들어서 정규분포 곡선 그리는 게 국룰이다. 누가 그러디 내가 

 

> ggplot(dat,aes(x=rating))+geom_histogram()
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

아, 이게 디폴트다. 

 

> ggplot(dat,aes(x=rating))+geom_density()

이건 평범한 밀도곡선. 

 

> ggplot(dat,aes(x=rating))+geom_histogram(aes(y=..density..),binwidth=.5,colour="black",fill="#939597")+geom_density(alpha=.2,fill="#f5df4d")

이렇게 하면 겹치는 것도 된다. 

 

> ggplot(dat,aes(x=rating))+geom_histogram()+geom_vline(aes(xintercept=mean(rating,na.rm=TRUE)))
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

평균에 선도 그어준다. (밀도곡선도 된다)

근데 사람이 살다보면 복식 히스토그램 그릴 수도 있잖아요! 예 그렇죠. 

 

> ggplot(dat,aes(x=rating,fill=comd))+geom_histogram(binwidth=.5,alpha=.5,position="identity")

이렇게 투명도를 줘서 겹쳐 그리는 방법도 있고 

 

> ggplot(dat,aes(x=rating,fill=comd))+geom_histogram(binwidth=.5,alpha=.5,position="dodge")

이렇게 닷지로 그리는 법도 있다. 개인적으로는 전자요. 

 

> ggplot(dat, aes(x=rating, fill=comd)) + geom_density(alpha=.3)

물론 밀도곡선도 된다. 

 

> library(plyr)
> cdat=ddply(dat,"comd",summarise,rating.mean=mean(rating))
> ggplot(dat,aes(x=rating,fill=comd))+geom_histogram(binwidth=.5,alpha=.5,position="dodge")+geom_vline(data=cdat,aes(xintercept=rating.mean,colour=comd))

개별로 선 긋는것도 된다. (왜)

 

> ggplot(dat, aes(x=rating)) + geom_histogram(binwidth=.5, colour="black", fill="white") + 
+     facet_grid(comd ~ .)

이거는 Facet 파트에서 자세하게 설명할건데 이것도 된다. 

 

박스 그래프(geom_boxplot())

> ggplot(dat, aes(x=comd, y=rating)) + geom_boxplot()

평범한 box plot은 이렇게 생겼다. 

 

> ggplot(dat, aes(x=comd, y=rating,fill=comd)) + geom_boxplot()+coord_flip()

그래서 눕혀드렸습니다^^ (coord_flip=xv축 값을 바꾼다)

 

> ggplot(dat, aes(x=comd, y=rating,fill=comd)) + geom_boxplot()+stat_summary(fun.y=mean,geom="point",size=3,shape=5)
경고메시지(들): 
`fun.y` is deprecated. Use `fun` instead.

아 평균도 찍어준다니까요 

 

scatter plot(geom_point())

저 포인트 꺾은선에서 본 것 같다고? 쟤는 라인이랑 둘이 쓰면 꺾은선 그래프에 점 찍어주고, 단독으로 쓰면 산점도다. 

참고로 예전에 python 하면서도 설명했나 모르겠는데 산점도는 XY축 값이 다 있어야 한다. 

 

> dat <- data.frame(cond = rep(c("A", "B"), each=10),
+                   xvar = 1:20 + rnorm(20,sd=3),
+                   yvar = 1:20 + rnorm(20,sd=3))
> dat
   cond         xvar         yvar
1     A -4.252354091  3.473157275
2     A  1.702317971  0.005939612
3     A  4.323053753 -0.094252427
4     A  1.780628408  2.072808278
5     A 11.537348371  1.215440358
6     A  6.672130388  3.608111411
7     A  0.004294848  7.529210289
8     A  9.971403007  6.156154355
9     A  9.007456032  8.935238147
10    A 11.766997972  8.928092187
11    B  8.840215645 13.202410972
12    B  5.974093783 17.644890794
13    B 15.034828849 10.485402010
14    B 10.985009895 10.138043066
15    B 13.543221961 18.681876114
16    B 11.435789493 19.143471805
17    B 16.977063388 18.832504955
18    B 17.220012698 18.939818864
19    B 17.793359218 19.718587761
20    B 19.319909163 19.647899863

그래서 난수가 1+1이 되었습니다. 

 

> ggplot(dat,aes(x=xvar,y=yvar))+geom_point(shape=1)

펑범한 산점도

 

> ggplot(dat,aes(x=xvar,y=yvar))+geom_point(shape=5)+geom_smooth(method=lm,se=FALSE)
`geom_smooth()` using formula 'y ~ x'

에 regression line을 얹어서 드셔보세요! 그거 먹는거 아냐 

 

> ggplot(dat,aes(x=xvar,y=yvar))+geom_point(shape=5)+geom_smooth(method=lm)
`geom_smooth()` using formula 'y ~ x'

뭔지 모르겠는것도 같이 말아서 드셔보세요! 

 

> ggplot(dat,aes(x=xvar,y=yvar))+geom_point(shape=5)+geom_smooth()
`geom_smooth()` using method = 'loess' and formula 'y ~ x'

이거 뭔데 애가 흐물흐물해짐??? 

근데 사람이 살다보면 산점도를 막 그룹별로 그리기도 한다 그죠? 

> ggplot(dat,aes(x=xvar,y=yvar,color=cond))+geom_point(size=3)

그렇다고 

 

> ggplot(dat,aes(x=xvar,y=yvar,color=cond))+geom_point(size=3)+scale_color_manual(values=c("#f5df4d","#939597"))+geom_smooth(method=lm,se=FALSE)
`geom_smooth()` using formula 'y ~ x'

 

> ggplot(dat,aes(x=xvar,y=yvar,color=cond))+geom_point(size=3)+scale_color_manual(values=c("#f5df4d","#939597"))+geom_smooth(method=lm,se=FALSE,fullrange=TRUE)
`geom_smooth()` using formula 'y ~ x'

아 회귀곡선도 낭낭하게 따로 넣어드린다니까요 

 

> ggplot(dat,aes(x=xvar,y=yvar,shape=cond,color=cond))+geom_point(size=3)

아 점 모양도 낭낭하게 바꿔드린다니까요 

오버플로팅이 뭔지는 모르겠으나 

> dat$xrnd=round(dat$xvar/5)*5
> dat$yrnd=round(dat$yvar/5)*5
> ggplot(dat,aes(x=xrnd,y=yrnd))+geom_point()

반올림했더니 그래프가 뭐야 내 산점도 돌려줘요가 될 때가 있다. 

 

> ggplot(dat,aes(x=xrnd,y=yrnd))+geom_point(alpha=1/4)

점에 알파를 줘 보면 겹친 영역에 따라 색이 다른 것이 보인다. (진한색이면 거기 겹쳐진 게 많은 거)

 

> ggplot(dat,aes(x=xrnd,y=yrnd))+geom_point(size=3,position=position_jitter(width=1,height=.5))

닷지 줘도 대충 보이시져? 

파일 관련 기능이 다 빠졌습니다. 아니 쓰는게 노가다여 아주. 


둘 다 이식 하긴 했는데 다른데서 개고생함… ㅋㅋㅋㅋㅋ 그래요 개고생이 끼어야 코딩이지… 

한번에 되면 그게 이상한거지… 

 

Cutter

그... 왜 Searcher에서 라디오버튼 선택하면 값 전송하는 건 다들 아실거고... 이걸 Cutter에도 적용해야 한다. 물론 Finder에도 적용할거다. 이게 왜 필요하냐면 입력 수단이 라디오버튼이기 때문. 그리고 라디오버튼에 각각 딸려오는 요소가 있어서, 연결된 라디오버튼이 선택되지 않았을 때 비활성화도 시켜줘야 한다. 

이거 근데 Finder 가면서 소스 갈아엎음. ㅇㅇ 

 

function activate() {
    const checked_input = document.querySelectorAll('#typing')
    const textarea = document.querySelectorAll('textarea')
    for (let i = 0; i < checked_input.length; i++) {
        if (checked_input[i].checked == true) {
            textarea[i].disabled = false;
            textarea[i].focus();
        } else {
            textarea[i].disabled = true;
        }
    }
}

function upfasta() {
    const checked_fasta = document.querySelectorAll('#FASTA')
    const upload_fasta = document.querySelectorAll('#input-FASTA')
    for (let i = 0;i < checked_fasta.length;i++) {
        if (checked_fasta[i].checked == true) {
            upload_fasta[i].disabled = false;
        }
        else {
            upload_fasta.disabled = true;
        }
    }
}

function upgen() {
    const checked_gen = document.querySelectorAll('#Genbank')
    const upload_gen = document.querySelectorAll('#input-Genbank')
    for (let i = 0;i < checked_fasta.length;i++) {
        if (checked_gen[i].checked == true) {
            upload_gen[i].disabled = false;
        }
        else {
            upload_gen.disabled = true;
        }
    }
}

소스가 왜 바꼈냐면 Finder 이식하면서 직접 입력/FASTA/Genbank가 아니라 커터/파인더로 나눴기 때문이다.

아무튼 Cutter에도 Searcher에 들어가는 라디오버튼이 일부 있다. NEB filter와 cut별 필터가 그거. 그니까 이번에 Ajax를 통해서 보내는 값은 

1. 입력한 시퀀스
2. NEB filter 선택값
3. Cut 엔딩 선택값

이렇게 세 개다. 

 

function cutFilter() {
    const NEB_filter = document.getElementsByName('option_NEB');
    const cut_filter = document.getElementsByName('option_cut');
    keyword_val = document.getElementById('keywd').value;

    for (let i = 0; i < NEB_filter.length; i++) {
        if (NEB_filter[i].checked == true) {
            NEB_filter_val = NEB_filter[i].value;
        }
    }
    for (let i = 0; i < cut_filter.length; i++) {
        if (cut_filter[i].checked == true) {
            cut_filter_val = cut_filter[i].value
        }
    }
}

그리고 필터값과 달리 시퀀스는 이미 직접 입력하겠다고 라디오버튼으로 정한거라 걍 텍스트에리어 값을 보내면 된다. 

 

$.ajax({
    type: "POST",
    url: "/cutter",
    data: {
        NEB_give: NEB_filter_val,
        cut_give: cut_filter_val,
        sequence_give: sequence
    },
    success: function (response) {
        alert(response['msg'])
    }
})

그럼 Ajax 드가자! 

참고로 얘는 로직 이식할 때 개고생했다. 값은 생각보다 수월하게 가져왔는데... Flask에 갖다놨더니 애가 전역변수를 인식을 못해서 함수로 뺐던거 다시 코드에 넣었다. 

 

Finder

​얘는 로직 이식은 생각보다 수월한데 반대로 JS쪽에서 개고생했다. 뭐가 불만인지 라디오버튼을 선택해도 세상에 활성이 안되잖어... 그래서 커터/파인더별로 묶었다. 용도별로 묶었더니 checked_input[i]가 체크 된 건 인식하는데 i번째 오브젝트 disabled가 안 풀렸거든... 

 

const checked_input = document.querySelectorAll('#typing');
const input_enz = document.getElementsByName('enzyme');
const textarea = document.querySelectorAll('textarea');
const checked_fasta = document.querySelectorAll('#FASTA');
const checked_gen = document.querySelectorAll('#Genbank');
const NEB_filter = document.getElementsByName('option_NEB');
const cut_filter = document.getElementsByName('option_cut');
let sequence = '';
if (checked_input[1].checked == true) {
    sequence = textarea[1].value;
}
for (let i = 0; i < NEB_filter.length; i++) {
    if (NEB_filter[i].checked == true) {
        NEB_filter_val = NEB_filter[i].value;
    }
}
for (let i = 0; i < cut_filter.length; i++) {
    if (cut_filter[i].checked == true) {
        cut_filter_val = cut_filter[i].value
    }
}

그것만 빼면 괜찮아서 입력 갈아엎고 스무th하게... 가긴 무슨... Finder는 이 효소가 이 시퀀스를 자르는가를 보는거라 저 필터들이 아예 없다. 그래서 저기 라디오버튼 걸려있는 필터들을 죄다 빼야 한다. 그러니까, 저기 있는 for문  두 개 얘기한거다. 

 

function findFilter() {
    const input_enz = document.getElementById('enzyme').value;
    const textarea = document.querySelectorAll('textarea');
    const checked_fasta = document.querySelectorAll('#FASTA');
    const checked_gen = document.querySelectorAll('#Genbank');
    let sequence = textarea[2].value
    console.log(input_enz,sequence)

    $.ajax({
        type: "POST",
        url: "/finder",
        data: {
            sequence_give: sequence,
            enzyme_give: input_enz
        },
        success: function (response) {
            console.log(response)
        }
    })

}

그래서 입력 박스 두 개의 값만 전달하면 된다. 참 쉽죠? 

참고로 저게 다라 이식 자체는 싱겁게 끝났다. 전역변수 없는건 커터덕에 알아서 바로 수정했고... 
근데 format땜시 {}준걸 왜 니가 나눠놓고 인식을 못하냐 파이참... 

 

완성작

Cutter
Finder

아니 눈누도 웹폰트가 되더라고 세상에... 

'Coding > Python' 카테고리의 다른 글

RE with FLASK-유효성 검사  (0) 2022.08.24
RE with FLASK-시퀀스 정보  (0) 2022.08.23
RE with FLASK-Searcher 이식하기  (0) 2022.08.22
RE with FLASK-뼈대 대공사  (0) 2022.08.22
Wordcloud with FLASK-뼈대 대공사 (3)  (0) 2022.08.22

들어가기 전에 중요한 게 하나 있다. 

Cutter와 Finder도 내일 이식하긴 할건데, 내일 이식하는 부분에서 FASTA/Genbank 파일 올리는거랑 결과 저장 기능은 빠진다. 그래서 원본 코드에 있던 with 어쩌고를 일단 작성 없이 서버로 보내서 텍스트 에리어에서 출력하는 걸 일차적으로 진행하고 저장-업로드 순으로 할 예정. FASTA/Genbank는 JS로 파일 받아서 파이썬으로 넘겨서 열 생각인데 그거 관련해서 로직 처리가 좀 필요하고(뼈대를 보면 라디오 버튼이다) 반대로 텍스트 파일은 파이썬에서 만들어서 JS로 넘길 생각이다. 텍스트 파일은 만들어준 거 받아서 버튼 누르면 다운로드 되게 하면 끝임. 


대공사 전에... 

일단 공사 전인 Searcher의 뼈대를 보자. 

원래 코드에서 입력으로 받던 걸 죄다 라디오버튼으로 박아버렸다. JS파일의 임무는 일차적으로 

1) 사용자가 입력한 검색어와 
2) 라디오버튼의 값을 app.py(파이썬)으로 전달해주는 

것이다. 로직은 뒤에서 돌 거니까 JS는 

1) 로직을 돌리기 위해 필요한 변수들을 주고
2) 결과물을 받아서 출력한다 

그리고 인자와 결과물의 전달을 Ajax가 한다. (얘때문에 jQuery 들어간거지 저거 빼고 어지간한 기능은 다 바닐라JS다)

 

function searchFilter() {
    const search_method = document.getElementsByName('search_val');
    console.log(search_method)
}

일단 이렇게 하면 값은 나오는데 문제는 뭐냐... 라디오버튼 중 선택된 값만 넘겨야 한다. 

 

function searchFilter() {
    const search_method = document.getElementsByName('search_val');
    for (let i = 0;i < search_method.length;i++) {
        if (search_method[i].checked == true) {
            console.log(search_method[i].value)
        }
    }
}

그러니까 이 if를 세 개 줘야 한다. 

 

function searchFilter() {
    const search_method = document.getElementsByName('search_val');
    const NEB_filter = document.getElementsByName('option_NEB');
    const cut_filter = document.getElementsByName('option_cut');

    for (let i = 0;i < search_method.length;i++) {
        if (search_method[i].checked == true) {
            console.log(search_method[i].value);
        }
    }
    for (let i = 0;i < NEB_filter.length;i++){
        if (NEB_filter[i].checked == true) {
            console.log(NEB_filter[i].value);
        }
    }
    for (let i = 0;i < cut_filter.length;i++){
        if (cut_filter[i].checked == true) {
            console.log(cut_filter[i].value)
        }
    }
}

왜 for+if가 3개인지 궁금하신 분들은 뼈대를 다시 보고 옵시다. 아무튼 그럼 Ajax를 불러보자. 

 

let search_method_val = '';
let NEB_filter_val = '';
let cut_filter_val = '';
function searchFilter() {
    const search_method = document.getElementsByName('search_val');
    const NEB_filter = document.getElementsByName('option_NEB');
    const cut_filter = document.getElementsByName('option_cut');

    for (let i = 0; i < search_method.length; i++) {
        if (search_method[i].checked == true) {
            search_method_val = search_method[i].value;
        }
    }
    for (let i = 0; i < NEB_filter.length; i++) {
        if (NEB_filter[i].checked == true) {
            NEB_filter_val = NEB_filter[i].value;
        }
    }
    for (let i = 0; i < cut_filter.length; i++) {
        if (cut_filter[i].checked == true) {
            cut_filter_val = cut_filter[i].value
        }
    }
    $.ajax({
    type: "POST",
    url: "/searcher",
    data: {NEB_give: NEB_filter_val, cut_give: cut_filter_val, search_give: search_method_val},
    success: function (response) {
        alert(response['msg'])
    }
})
}

여기다가 변수 하나만 추가하면 텍스트도 같이 넘길 수 있다. 

 

@app.route('/searcher', methods=['POST'])
def searcher():
    NEB_receive = request.form['NEB_give']
    cut_receive = request.form['cut_give']
    search_receive = request.form['search_give']
    message = '{},{},{}'.format(NEB_receive,cut_receive,search_receive)
    return jsonify({'msg': message})

그리고 받아오면 된다. 

 

로직의 변경점

위에서 기존 코드로 입력받던 것들을 라디오버튼으로 바꿨다고 했는데, 그거 말고도 변경점이 하나 더 있다. 원래 코드에서는 print문으로 출력해줬지만, 쟤는 app.py에서 다 만들고 나면 결과를 다시 JS에게 넘겨줘야 한다. 그러니까 단순히 print문으로 되어있던 걸 처리하는 과정이 필요하다.

 

@app.route('/searcher', methods=['POST'])
def searcher():
    enzyme_table = pd.read_csv('/home/koreanraichu/restriction_merge.csv')
    NEB_receive = request.form['NEB_give']
    cut_receive = request.form['cut_give']
    search_receive = request.form['search_give']
    keywd_receive = request.form['keywd_give']

    def SeqtoString(a):
        a = enzyme_table.sequence[(enzyme_table['Enzyme'] == keywd_receive)]
        a = a.to_string(index=False)
        a = str(a).strip()
        return a

    def SitetoString(a):
        a = enzyme_table.restriction_site[(enzyme_table['Enzyme'] == keywd_receive)]
        a = a.to_string(index=False)
        a = str(a).strip()
        return a

    def NEB_selling(a):
        a = enzyme_table.NEB_sell[(enzyme_table['Enzyme'] == keywd_receive)]
        a = a.to_string(index=False)
        a = str(a).strip()
        return a

    # 함수 가즈아!!!

    if cut_receive == 'sticky':
        enzyme_table = enzyme_table[enzyme_table['cut_feature'] == 'Sticky']
        enzyme_table.reset_index(inplace=True)
        cut_feature = 'Sticky end'
    elif cut_receive == 'blunt':
        enzyme_table = enzyme_table[enzyme_table['cut_feature'] == 'Blunt']
        enzyme_table.reset_index(inplace=True)
        cut_feature = 'Blunt end'
    elif cut_receive == 'nicked':
        enzyme_table = enzyme_table[enzyme_table['cut_feature'] == 'Nicked']
        enzyme_table.reset_index(inplace=True)
        cut_feature = "Nicked"
    else:
        cut_feature = "W/O filter"
        pass
    if NEB_receive == 'NEB':
        enzyme_table = enzyme_table[enzyme_table['NEB_sell'] == 'Yes']
        enzyme_table.reset_index(inplace=True)
        NEB_filter = 'NEB only'
    else:
        NEB_filter = 'All'
        pass
    if search_receive == 'name':
        pass
    elif search_receive == 'sequence':
        pass
    else:
        pass
    return jsonify({'msg': '입력되었습니다!'})

근데 함수 그냥 이렇게 써도 됨? 

아무튼 그래서... print문을 전부 텍스트로 할당했다. 

@app.route('/searcher', methods=['POST'])
def searcher():
    enzyme_table = pd.read_csv('/home/koreanraichu/restriction_merge.csv')
    NEB_receive = request.form['NEB_give']
    cut_receive = request.form['cut_give']
    search_receive = request.form['search_give']
    keyword_receive = request.form['keyword_give']

    def SeqtoString(a):
        a = enzyme_table.sequence[(enzyme_table['Enzyme'] == keyword_receive)]
        a = a.to_string(index=False)
        a = str(a).strip()
        return a

    def SitetoString(a):
        a = enzyme_table.restriction_site[(enzyme_table['Enzyme'] == keyword_receive)]
        a = a.to_string(index=False)
        a = str(a).strip()
        return a

    def NEB_selling(a):
        a = enzyme_table.NEB_sell[(enzyme_table['Enzyme'] == keyword_receive)]
        a = a.to_string(index=False)
        a = str(a).strip()
        return a

    # 함수 가즈아!!!

    if cut_receive == 'sticky':
        enzyme_table = enzyme_table[enzyme_table['cut_feature'] == 'Sticky']
        enzyme_table.reset_index(inplace=True)
        cut_feature = 'Sticky end'
    elif cut_receive == 'blunt':
        enzyme_table = enzyme_table[enzyme_table['cut_feature'] == 'Blunt']
        enzyme_table.reset_index(inplace=True)
        cut_feature = 'Blunt end'
    elif cut_receive == 'nicked':
        enzyme_table = enzyme_table[enzyme_table['cut_feature'] == 'Nicked']
        enzyme_table.reset_index(inplace=True)
        cut_feature = "Nicked"
    else:
        cut_feature = "W/O filter"
        pass
    if NEB_receive == 'NEB':
        enzyme_table = enzyme_table[enzyme_table['NEB_sell'] == 'Yes']
        enzyme_table.reset_index(inplace=True)
        NEB_filter = 'NEB only'
    else:
        NEB_filter = 'All'
        pass

    if search_receive == 'name':
        find_seq = SeqtoString(keyword_receive)
        Site_seq = SitetoString(keyword_receive)
        NEB_sell = NEB_selling(keyword_receive)
        result_iso = ''
        result_neo = ''
        result_iso_list = []
        result_neo_list = []
        Iso = []
        Neo = []
        message_give =  "{0} | {1} | {2} | {3} | Input enzyme".format(keyword_receive, find_seq, Site_seq, NEB_sell)
        for i in range(len(enzyme_table)):
            DB_enzyme = str(enzyme_table['Enzyme'][i]).strip()
            DB_seq = str(enzyme_table['sequence'][i]).strip().upper()
            DB_site = str(enzyme_table['restriction_site'][i]).strip().upper()
            if find_seq == str(DB_seq) and DB_enzyme != keyword_receive:
                if Site_seq == DB_site:
                    Iso.append(DB_enzyme)
                    result_iso = "{0} | {1} | {2} | {3} | Isoschizomer".format(DB_enzyme, DB_seq, DB_site,
                                                                                   NEB_sell)
                    result_iso_list.append(result_iso)

                    # 인식하는 시퀀스와 자르는 방식이 같은 제한효소
                elif Site_seq != DB_site:
                    Neo.append(DB_enzyme)
                    result_neo = "{0} | {1} | {2} | {3} | Neoschizomer".format(DB_enzyme, DB_seq, DB_site,
                                                                                   NEB_sell)
                    result_neo_list.append(result_neo)
                    # 인식하는 시퀀스는 같으나 자르는 방식이 다른 제한효소
            elif find_seq == str(DB_seq) and DB_enzyme == keyword_receive:
                pass
            else:
                pass
    elif search_receive == 'sequence':
        find_seq = keyword_receive
        message_give = ("Searched by: {0}".format(keyword_receive))
        for i in range(len(enzyme_table)):
            DB_enzyme = str(enzyme_table['Enzyme'][i]).strip()
            DB_seq = str(enzyme_table['sequence'][i]).strip().upper()
            DB_site = str(enzyme_table['restriction_site'][i]).strip().upper()
            DB_NEB = str(str(enzyme_table['NEB_sell'][i]).strip())
            if find_seq == DB_seq:
                print("{0} | {1} | {2} | {3}".format(DB_enzyme, DB_seq, DB_site, DB_NEB))
        else:
            enzyme_RE = keyword_receive
            enzyme_RE = enzyme_RE.upper()
            enzyme_RE_2 = '^' + enzyme_RE
            message_give = ("Enzyme with start with {0}".format(enzyme_RE))
            for i in range(len(enzyme_table)):
                DB_enzyme = str(enzyme_table['Enzyme'][i]).strip()
                DB_seq = str(enzyme_table['sequence'][i]).strip().upper()
                DB_site = str(enzyme_table['restriction_site'][i]).strip().upper()
                DB_NEB = str(str(enzyme_table['NEB_sell'][i]).strip())
                if re.search(enzyme_RE_2, DB_enzyme):
                    print("{0} | {1} | {2} | {3}".format(DB_enzyme, DB_seq, DB_site, DB_NEB))
    return jsonify({'Message_give': message_give,'Result_iso':result_iso_list,'Result_neo':result_neo_list})

이건데(일단 이름 검색만 됨) 여기서 

if search_receive == 'name':
    find_seq = SeqtoString(keyword_receive)
    Site_seq = SitetoString(keyword_receive)
    NEB_sell = NEB_selling(keyword_receive)
    result_iso = ''
    result_neo = ''
    result_iso_list = []
    result_neo_list = []
    Iso = []
    Neo = []
    message_give =  "{0} | {1} | {2} | {3} | Input enzyme".format(keyword_receive, find_seq, Site_seq, NEB_sell)
    for i in range(len(enzyme_table)):
        DB_enzyme = str(enzyme_table['Enzyme'][i]).strip()
        DB_seq = str(enzyme_table['sequence'][i]).strip().upper()
        DB_site = str(enzyme_table['restriction_site'][i]).strip().upper()
        if find_seq == str(DB_seq) and DB_enzyme != keyword_receive:
            if Site_seq == DB_site:
                Iso.append(DB_enzyme)
                result_iso = "{0} | {1} | {2} | {3} | Isoschizomer".format(DB_enzyme, DB_seq, DB_site,
                                                                               NEB_sell)
                result_iso_list.append(result_iso)

                # 인식하는 시퀀스와 자르는 방식이 같은 제한효소
            elif Site_seq != DB_site:
                Neo.append(DB_enzyme)
                result_neo = "{0} | {1} | {2} | {3} | Neoschizomer".format(DB_enzyme, DB_seq, DB_site,
                                                                               NEB_sell)
                result_neo_list.append(result_neo)
                # 인식하는 시퀀스는 같으나 자르는 방식이 다른 제한효소

이 부분을 다시 보다. print문이 있어야 할 곳에 메세지, 리절트 이런 변수들이 있다. 왜 이렇게 했나고? 

return jsonify({'Message_give': message_give,'Result_iso':result_iso_list,'Result_neo':result_neo_list})

얘때문임. Ajax로 다시 JS로 보내려면 JSON파일로 만들어야 하거든… 그래서 결과를 딕셔너리로 만든 다름 JSON으로 넘길거다. 

 

success : function (response) {
    var message = response['Message_give']
    var iso = response['Result_iso']
    var neo = response['Result_neo']
    let result = document.getElementById('search_result');
    result.innerText = message;
    for (let i = 0; i < iso.length; i++) {
        console.log(iso[i])
        result.innerText = iso[i]
    }
    for (let i = 0; i < neo.length; i++) {
        console.log(neo[i])
        result.innerText = neo[i]
    }
}

근데 여기서 줄바꿈 안돼서 진짜 온갖가지 것들을 다 해봤음... 근데 이스케이프 문자 안먹지 백틱 안먹지 뭐 어쩌라는건지...ㅡㅡ 

 

success : function (response) {
    if (search_method_val == 'name') {
        let message = response['doc_result']['Message_give'];
        let iso = response['doc_result']['Result_iso'];
        let neo = response['doc_result']['Result_neo'];
        let result = document.getElementById('search_result');
        result.innerHTML = message;
        for (let i = 0; i < iso.length; i++) {
            result.innerHTML = result.innerHTML.concat("\n", iso[i])
        }
        for (let i = 0; i < neo.length; i++) {
            result.innerHTML = result.innerHTML.concat("\n", neo[i])
        }
    } else if (search_method_val == 'sequence') {
        let message = response['doc_result']['Message_give'];
        let same_seq = response['doc_result']['Result_seq'];
        let result = document.getElementById('search_result');
        result.innerHTML = message;
        for (let i = 0; i < same_seq.length; i++) {
            result.innerHTML = result.innerHTML.concat("\n", same_seq[i])
        }
    } else {
        let message = response['doc_result']['Message_give'];
        let spell = response['doc_result']['Result_spell'];
        let result = document.getElementById('search_result');
        result.innerHTML = message;
        for (let i = 0; i < spell.length; i++) {
            result.innerHTML = result.innerHTML.concat("\n", spell[i])
        }
    }
}

는 지나가던 분의 .concat()신공과 가만 이거 innerText말고 inner HTML도 있잖아?로 해⭐결! 

 

그래서 완성품

이름으로 검색
시퀀스로 검색
그 뭐더라 X로 시작하는 효소

 

이후 추가할 기능

검색어 없을 때 멘트랑 검색 방법별 도움말정도? 

'Coding > Python' 카테고리의 다른 글

RE with FLASK-시퀀스 정보  (0) 2022.08.23
RE with FLASK-Cutter/Finder  (0) 2022.08.22
RE with FLASK-뼈대 대공사  (0) 2022.08.22
Wordcloud with FLASK-뼈대 대공사 (3)  (0) 2022.08.22
경로때문에 개노가다 한 썰 푼다  (0) 2022.08.22

아, RE는 Restriction enzyme이다. 


기본뼈대

app.py

from flask import Flask,render_template
import pandas as pd
import re
from datetime import datetime
from argparse import FileType
import tkinter
from tkinter import filedialog
from Bio import SeqIO
import os
import platform

app = Flask(__name__)


@app.route('/')
def hello_world():  # put application's code here
    return render_template('index.html')


if __name__ == '__main__':
    app.run()

벌써 모듈 정모 들어간거 실화냐. 

 

HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="/static/style.css">
    <title>Project restriction enzyme</title>
</head>
<body>
<div class="wrap">
<h1>Project restriction enzyme</h1>
    <div class="tab_re">
        <ul class="list">
            <li class="focus">
                <a href="#tab1" class="btn">About Project restriction enzyme</a>
                <div id="tab1" class="cont"></div>
            </li>
            <li>
                <a href="#tab2" class="btn">Restriction enzyme Cutter</a>
                <div id="tab2" class="cont"></div>
            </li>
            <li>
                <a href="#tab3" class="btn">Restriction enzyme Finder</a>
                <div id="tab3" class="cont"></div>
            </li>
            <li>
                <a href="#tab4" class="btn">Restriction enzyme searcher</a>
                <div id="tab4" class="cont"></div>
            </li>
        </ul>
    </div>
</div>
<script src="/static/script.js"></script>
</body>
</html>

 

CSS

@import url('https://fonts.googleapis.com/css2?family=Gamja+Flower&display=swap');

* {
    margin:0;
    padding:0;
    font-family: 'Gamja Flower', cursive;
    font-size:16pt;
    color:#02343F;
}

ul {
    list-style-type:none;
}

a{
    text-decoration:none;
    color:#02343F;
}

h1 {
    text-align:center;
    font-size:27pt;
    margin:15px auto;
}

h2 {
    font-size:18pt;
    margin-bottom:5px;
}

h3{
    margin-bottom:5px;
}

img {
    width:500px;
}

span {
    text-align:center;
    display:block;
    font-size:16pt;
}

p{
    margin-bottom:20px;
}

.image{
    margin:5px auto;
    width:500px;
}
.wrap {
    width:1200px;
    margin:0 auto;
    padding:15px;
    letter-spacing:-0.5px;
}

.tab_re {
    position:relative;
}

.tab_re .list {
    overflow:hidden;
}

.tab_re li {
    float:left;
}

.tab_re .list .btn {
    font-size:17pt;
    color:#02343F;
    width:300px;
    height:50px;
    display:inline-block;
    text-align:center;
    line-height:50px;
    background-color:#fefefe;
    border-bottom:2px solid #fefefe;
}

.tab_re .list .cont {
    font-size:15pt;
    display:none;
    position:absolute;
    left:0;
    background-color:white;
    color:#fff;
    width:1200px;
    height:auto;
    line-height:30px;
    padding:20px;
    box-sizing:border-box;
}

.tab_re .list li.focus .btn {
    background:linear-gradient(to bottom,#ffffff,#F0EDCC);
    border-bottom:2px solid #02343F;
    color:#02343F;
    display:block;
}

.tab_re .list li.focus .cont {
    display:block;
    background-color:#F0EDCC;
    color:#000;
}

 

JS

const tabList = document.querySelectorAll('.tab_re .list li');
console.log(tabList)

for(var i = 0; i < tabList.length; i++){
    tabList[i].querySelector('.btn').addEventListener('click', function(e){
      e.preventDefault();
      for(var j = 0; j < tabList.length; j++){
        tabList[j].classList.remove('focus');
      }
      this.parentNode.classList.add('focus');
    });
  }

얘는 이거 말고 크게 건드릴건 없... 아 있구나 데이터 넘겨야지... 

 

확장 뼈대

각 뼈대는 여기서 파생된다. 그러니까 이게 기본형. 

 

Cutter

시퀀스의 경우 라디오버튼으로, FASTA와 Genbank는 업로드할거고 위는 직접 입력이다. 기존에 코드에서 수기로 입력받던 게 라디오버튼화 됐다고 보면 된다. 아래쪽 텍스트에리어는 readonly를 줘서 편집은 안된다. 근데 결과 출력란이라 편집하면 안된다. 

결과 저장하기는 저기 보이는 결과를 텍스트파일로 저장할 수 있는 버튼. 그 텍스트파일로 나가는 그거 맞다. 

 

Finder

Finder는 이 효소가 이걸 자르나? 를 보는거기때문에 제한효소명도 입력받는다. 

 

Searcher

저 닉필터 저거 커터에 넣어야지... 아무튼 Searcher는 검색 필터라고 해야 하나... 그 라디오버튼 먹일 게 더럽게 많다. 

Entrez쪽 뼈대 만든거랑 버튼 얘기 있으니까 기둘려보시오 


Entrez with wordcloud 뼈대

이 뼈대가 완전한 건 아닌게 글꼴 선택란이 없음.. 지금 생각중인건 어비 스윗체/둘기마요/나눔고딕정도... 아무튼 Entrez with wordcloud 뼈대도 만들었음. 

 

입력부

출력부는 별 차이 없고, 얘는 입력 인자가 검색어랑 논문 갯수뿐이라 입력란이 간소하다. 지금은 논문 개수 입력하는 란이 없는데 그건 나중에 만들면 되고… 

 

Sample text 버튼

Text with wordcloud는 찍먹이나 해보자 하는 분들을 위한 샘플 텍스트를 제공하고 있다. 한글 2 영어 2 생각중인데 영어 하나 제보좀 해주세요… 

 

<button type="button" class="sample" id="sample1" onclick="starcount()">샘플 텍스트(윤동주-별 헤는 밤)</button>
<button type="button" class="sample" id="sample2">샘플 텍스트(이육사-청포도)</button>
<button type="button" class="sample" id="sample3">샘플 텍스트 3</button>
<button type="button" class="sample" id="sample4">샘플 텍스트 4</button>

일단 HTML에 온클릭을 넣는다.

 

function bluegrape() {
    let contents = document.querySelector('textarea')
    const text = `내 고장 칠월은
청포도가 익어가는 시절

이 마을 전설이 주절이주절이 열리고
먼데 하늘이 꿈꾸며 알알이 들어와 박혀

하늘 밑 푸른 바다가 가슴을 열고
흰 돛단배가 곱게 밀려서 오면

내가 바라는 손님은 고달픈 몸으로
청포를 입고 찾아온다고 했으니

내 그를 맞아 이 포도를 따먹으면
두 손은 함뿍 적셔도 좋으련

아이야 우리 식탁엔 은쟁반에
하이얀 모시 수건을 마련해두렴`
    contents.textContent = text
}

그 다음 자바스크립트(스크립트 파일)로 넘어가서 함수를 이렇게 짜 주면 된다. (길이 제일 짧은게 저거) textarea를 가져와서 해당 텍스트를 컨텐츠로 채우는 식. 

참고로 영문 샘플 텍스트 하나는 그 뭐지? 그 시 있었는데 네버모어 하는... 그거다. 

일단 개고생한 이유를 축약해드리자면... 

1. 윈도우에서는 폰트 저장 경로(C:\Windows\Fonts)에 있는 걸 갖다 쓰는 게 아니라 TTF, OTF파일이 있는 경로를 직접 입력해야 갖다 쓸 수 있음. 
2. 근데 리눅스는 폰트 저장 경로(/usr/share/fonts)에서 선택해서 쓸 수 있음. 

그래서 개고생했습니다. 


OS = platform.platform()
if 'Linux' in OS: 
    default_dir = '/home'
    root = tkinter.Tk()
    root.withdraw()
    font_dir = '/usr/share/fonts'
    font_path = filedialog.askopenfilename(parent=root, initialdir=font_dir, title='Choose your fonts for Wordcloud',
                                           filetypes=(("*.ttf", "*ttf"), ("*.otf", "*otf")))
elif 'Windows' in OS:
    default_dir = 'C:\\'
    root = tkinter.Tk()
    root.withdraw()
    font_path = filedialog.askopenfilename(parent=root, initialdir=default_dir, title='Choose your fonts for Wordcloud',
                                           filetypes=(("*.ttf", "*ttf"), ("*.otf", "*otf")))

그래서 코드도 이걸로 수정했음. 아예 OS에 따라 폰트 파일을 여는 방식이 다르기때문에 리눅스는 폰트가 저장된 경로를 띄우고, 윈도우는 알아서 파일 갖다 여시라고 친절하게 C드라이브 띄워드림. 

워드클라우드부터 해서 진짜 얘까지 개 노가다의 연속이었음... 
그래도 이거 미리 해놔서 Entrez는 입력부만 조금 수정하면 됩니다. 

워드클라우드 경로 썰은 다른 포스트에서 풀어드림. 


이게 길어서 한번에 캡처 못했음다. 

입력부

입력부의 각 기능

1. 텍스트 입력란(샘플 텍스트도 있음): 샘플 텍스트는 현재 한글 두개(별 헤는 밤, 청포도)랑 영어 하나(미정)를 생각중이며, 버튼을 누르면 샘플 텍스트가 텍스트에리어에 입력된다. 길면 잘 뽑히긴 한데 영어는 뭘 해야 할 지 모르겠음. 아니 그렇다고 하이 잭 하이 마이크 하와유 이딴걸 할 순 없잖수. 논문 해 논문 
2. 컬러맵 선택(이따 이미지 올려드림): 이건 밑에 털어드림. 
3. 마스킹이미지 ㄱㄱ: 이미지 안 쓴다는 선택지는 없음. 
4. 언어 선택(한국어는 konlpy 한번 거쳐야 함)

즉 여기서 입력을 받아서 파이썬으로 전달하는 요소는 

1. 워드클라우드를 만들 텍스트
2. 컬러맵
3. 글꼴(...)
4. 마스킹 이미지
5. 한글이냐? (제일 간단)

이다. 

 

출력부

출력부는 개 심플하다. 저기에 워드클라우드 이미지가 뜨고 다운로드 버튼을 누르면 다운로드가 된다. 즉, 작업이 다 끝나서 이미지가 나오게 되면 파이썬에서 여기로 주면 된다. 

 

컬러맵

참고로 이거 일부분이다. matplotlib 컬러맵이 진짜 개많음... 살려주세요... 사랑해요 그리드뷰

워드클라우드를 만드는 소스 코드가 파이썬인데, 파이썬에서 워드클라우드를 만들 때 컬러맵은 matplotlib의 컬러맵을 가져온다. 그니까 저 컬러맵 matplotlib으로 그래프 그릴 때도 쓸 수 있다. 그래프만으로 논문이 컬러풀해지는 기적 저걸 근데 입력받으면 분명 오타냄... 그리고 오타나면 워드클라우드 에러뜸... 그래서 저걸 어쩔 수 없이 라디오버튼으로 박아놨다.

 

그리고 라디오버튼에 할당된 value가 저 이름 그대로 들어가기 때문에 선택하고 OK만 하면 value 그대로 넘겨서 하면 된다.

아니 나도 몰랐는데 파이썬으로 OS 정보를 볼 수 있더라? 근데 맥은 내가 가지고 있는 기기가 없어서 못봤음... 

import platform

일단 이놈을 데려온 다음 

print(platform.platform())

을 치면 자기 컴퓨터의 OS 정보를 볼 수 있다. 

 

그래서 이걸 어떻게 수정했냐면 

OS = platform.platform()
if 'Linux' in OS: 
    default_dir = '/home'
    font_dir = '/usr/share/fonts'
else:
    default_dir = 'C:\\'
    font_dir = 'C:\\Fonts'

OS 정보를 가져와서 리눅스면 기본 경로를 이렇게 하고, 아니면(윈도우면) 이렇게 해라 이렇게 했다. 위 코드는 워드클라우드고 제한효소쪽은 파일 불러오는것 말고 없어서 글꼴 경로는 생략. 

네? 맥이요? 기본경로 어케앎? 

'Coding > Python' 카테고리의 다른 글

경로때문에 개노가다 한 썰 푼다  (0) 2022.08.22
Wordcloud with FLASK-뼈대 대공사 (2)  (0) 2022.08.22
Wordcloud with FLASK-뼈대 대공사  (0) 2022.08.22
대형프로젝트 계획중  (0) 2022.08.22
input vs sys.stdin.readline()  (0) 2022.08.22

일단... 현재 완성된 부분이 

여긴데 어바웃 페이지만 된다. 입력받는 란은 아직 안함. 

현재 구현된 기능

1) 탭 메뉴(어바웃 페이지/텍스트/엔트레즈 탭)
2) 어바웃페이지


app.py

from flask import Flask, render_template
from Bio import Entrez
from wordcloud import WordCloud
from wordcloud import STOPWORDS
import matplotlib.pyplot as plot
from PIL import Image
import numpy as np
from argparse import FileType
import tkinter
from tkinter import filedialog
import re
from konlpy.tag import Okt
import nltk
okt=Okt()

app = Flask(__name__)


@app.route('/')
def hello_world():  # put application's code here
    return render_template('index.html')


if __name__ == '__main__':
    app.run()

모듈 정모 들어간 것 같다면 정상… 차후 로직 만지면서 저기 말고 개별 코드에 들어갈수도 있다. 참고로 본인은 파이썬 모듈 중구난방인 거 싫어함. 

 

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="/static/style.css">
    <title>Project wordcloud</title>
</head>
<body>

    <div class="wrap">
        <h1>Project wordcloud</h1>
        <div class="tab_wc">
            <ul class="list">
                <li class="focus">
                    <a href="#tab1" class="btn">About project wordcloud</a>
                    <div id="tab1" class="cont">어바웃 페이지입니다</div>
                </li>
                <li>
                    <a href="#tab2" class="btn">Wordcloud with text</a>
                    <div id="tab2" class="cont">Wordcloud with text</div>
                </li>
                <li>
                    <a href="#tab3" class="btn">Entrez with wordcloud</a>
                    <div id="tab3" class="cont">Entrez with wordcloud</div>
                </li>
            </ul>
        </div>
    </div>
<script src="/static/script.js"></script>
</body>
</html>

이제 코드단에서 손볼 건 내용말고 없다. ...없겠지? 

 

CSS

@import url('https://fonts.googleapis.com/css2?family=Yeon+Sung&display=swap');

* {
    margin:0;
    padding:0;
    font-family: 'Yeon Sung', cursive;
    font-size:15pt;
}

ul {
    list-style-type:none;
}

a{
    text-decoration:none;
}

h1 {
    text-align:center;
    font-size:25pt;
    margin:15px auto;
}

h2 {
    font-size:18pt;
    margin-bottom:5px;
}

h3{
    margin-bottom:5px;
}

img {
    width:500px;
}

span {
    text-align:center;
    display:block;
    font-size:15pt;
}

p{
    margin-bottom:20px;
}

.image{
    margin:5px auto;
    width:500px;
}
.wrap {
    width:1080px;
    margin:0 auto;
    padding:15px;
    letter-spacing:-0.5px;
}

.tab_wc {
    position:relative;
}

.tab_wc .list {
    overflow:hidden;
}

.tab_wc li {
    float:left;
}

.tab_wc .list .btn {
    font-size:16pt;
    color:#00498c;
    width:220px;
    height:50px;
    display:inline-block;
    text-align:center;
    line-height:50px;
    background-color:#fefefe;
    border-bottom:2px solid #fefefe;
}

.tab_wc .list .cont {
    font-size:14pt;
    display:none;
    position:absolute;
    left:0;
    background-color:white;
    color:#fff;
    width:1080px;
    height:auto;
    line-height:30px;
    padding:20px;
}

.tab_wc .list li.focus .btn {
    background-color:#f5f5f5;
    border-bottom:2px solid #00498c;
    color:#00498c;
    display:block;
}

.tab_wc .list li.focus .cont {
    display:block;
    background-color:#f5f5f5;
    color:#000;
}

input이나 라벨 등의 요소가 추가되면 뭔가 더 추가될 가능성이 있다. 

 

JS

const tabList = document.querySelectorAll('.tab_wc .list li');
console.log(tabList)

for(var i = 0; i < tabList.length; i++){
    tabList[i].querySelector('.btn').addEventListener('click', function(e){
      e.preventDefault();
      for(var j = 0; j < tabList.length; j++){
        tabList[j].classList.remove('focus');
      }
      this.parentNode.classList.add('focus');
    });
  }

탭 메뉴 관련 스크립트이다. 끝. 

'Coding > Python' 카테고리의 다른 글

Wordcloud with FLASK-뼈대 대공사 (2)  (0) 2022.08.22
Wordcloud/RE 경로 관련 코드 수정  (0) 2022.08.22
대형프로젝트 계획중  (0) 2022.08.22
input vs sys.stdin.readline()  (0) 2022.08.22
Python으로 JSON파일 읽기  (0) 2022.08.22

Profile

Lv. 34 라이츄

요즘 날씨 솔직히 에바참치김치꽁치갈치넙치삼치날치기름치준치학꽁치임..