diff --git a/2022/04/13/a.html b/2022/04/13/a.html index f4560bf7fe..d2c32ee21b 100644 --- a/2022/04/13/a.html +++ b/2022/04/13/a.html @@ -22,7 +22,7 @@ - + @@ -1781,8 +1781,8 @@ } - - + + + @@ -3735,7 +3735,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/04/14/a.html b/2022/04/14/a.html index 2966e0e033..f9a7bd09a8 100644 --- a/2022/04/14/a.html +++ b/2022/04/14/a.html @@ -22,7 +22,7 @@ - + @@ -1783,8 +1783,8 @@ } - - + + + @@ -3793,7 +3793,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/04/15/a.html b/2022/04/15/a.html index e54bfa11a9..da0d2b818f 100644 --- a/2022/04/15/a.html +++ b/2022/04/15/a.html @@ -22,7 +22,7 @@ - + @@ -1781,8 +1781,8 @@ } - - + + + @@ -3828,7 +3828,7 @@

- + @@ -3958,19 +3958,19 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/04/16/a.html b/2022/04/16/a.html index 66e2e2d3b3..fc88a6f94a 100644 --- a/2022/04/16/a.html +++ b/2022/04/16/a.html @@ -22,7 +22,7 @@ - + @@ -1781,8 +1781,8 @@ } - - + + + @@ -3770,7 +3770,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/04/16/b.html b/2022/04/16/b.html index c767594fd2..dbdff9a858 100644 --- a/2022/04/16/b.html +++ b/2022/04/16/b.html @@ -22,7 +22,7 @@ - + @@ -1781,8 +1781,8 @@ } - - + + + @@ -3782,7 +3782,7 @@

- + @@ -3912,19 +3912,19 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/04/17/a.html b/2022/04/17/a.html index 4ac9e1dead..4923edca9b 100644 --- a/2022/04/17/a.html +++ b/2022/04/17/a.html @@ -22,7 +22,7 @@ - + @@ -1782,8 +1782,8 @@ } - - + + + @@ -3792,7 +3792,7 @@

- + @@ -3922,19 +3922,19 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/04/17/b.html b/2022/04/17/b.html index 2609b2ea2d..9a06d27cd2 100644 --- a/2022/04/17/b.html +++ b/2022/04/17/b.html @@ -22,7 +22,7 @@ - + @@ -1781,8 +1781,8 @@ } - - + + + @@ -3769,7 +3769,7 @@

- + @@ -3899,19 +3899,19 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/04/18/a.html b/2022/04/18/a.html index 872cd4c805..995f0b4556 100644 --- a/2022/04/18/a.html +++ b/2022/04/18/a.html @@ -22,7 +22,7 @@ - + @@ -1783,8 +1783,8 @@ } - - + + + @@ -3781,7 +3781,7 @@

- + @@ -3911,19 +3911,19 @@

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3992,7 +3992,7 @@

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4112,7 +4112,7 @@

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4452,7 +4452,7 @@

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/04/20/a.html b/2022/04/20/a.html index fa761b9b58..088bd1c5bf 100644 --- a/2022/04/20/a.html +++ b/2022/04/20/a.html @@ -22,7 +22,7 @@ - + @@ -1781,8 +1781,8 @@ } - - + + + @@ -3791,7 +3791,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/04/20/b.html b/2022/04/20/b.html index 3796e2d9ab..33a5749159 100644 --- a/2022/04/20/b.html +++ b/2022/04/20/b.html @@ -22,7 +22,7 @@ - + @@ -1781,8 +1781,8 @@ } - - + + + @@ -3935,7 +3935,7 @@

- + @@ -4065,19 +4065,19 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/04/25/a.html b/2022/04/25/a.html index 04d45ed977..b23907266d 100644 --- a/2022/04/25/a.html +++ b/2022/04/25/a.html @@ -22,7 +22,7 @@ - + @@ -1783,8 +1783,8 @@ } - - + + + @@ -3793,7 +3793,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/04/29/a.html b/2022/04/29/a.html index 7ba2a73c82..56b91615f3 100644 --- a/2022/04/29/a.html +++ b/2022/04/29/a.html @@ -22,7 +22,7 @@ - + @@ -1782,8 +1782,8 @@ } - - + + + @@ -3788,7 +3788,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/05/03/a.html b/2022/05/03/a.html index 845473a4b9..42affcd25e 100644 --- a/2022/05/03/a.html +++ b/2022/05/03/a.html @@ -22,7 +22,7 @@ - + @@ -1782,8 +1782,8 @@ } - - + + + @@ -3774,7 +3774,7 @@

- + @@ -3904,19 +3904,19 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/05/04/a.html b/2022/05/04/a.html index e833ffaee0..34b81fc916 100644 --- a/2022/05/04/a.html +++ b/2022/05/04/a.html @@ -22,7 +22,7 @@ - + @@ -1783,8 +1783,8 @@ } - - + + + @@ -3793,7 +3793,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/05/05/a.html b/2022/05/05/a.html index 336a9dd6b5..90b5c4df94 100644 --- a/2022/05/05/a.html +++ b/2022/05/05/a.html @@ -22,7 +22,7 @@ - + @@ -1782,8 +1782,8 @@ } - - + + + @@ -3944,7 +3944,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/05/08/a.html b/2022/05/08/a.html index 68fb6ca0c0..9e55a5a3ac 100644 --- a/2022/05/08/a.html +++ b/2022/05/08/a.html @@ -22,7 +22,7 @@ - + @@ -1784,8 +1784,8 @@ } - - + + + @@ -3767,7 +3767,7 @@

1 - + @@ -3897,19 +3897,19 @@

1 const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3978,7 +3978,7 @@

1 await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4098,7 +4098,7 @@

1 function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4438,7 +4438,7 @@

1 var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/05/16/a.html b/2022/05/16/a.html index e3b50fbf30..323ce5be11 100644 --- a/2022/05/16/a.html +++ b/2022/05/16/a.html @@ -22,7 +22,7 @@ - + @@ -1783,8 +1783,8 @@ } - - + + + @@ -3793,7 +3793,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/05/25/a.html b/2022/05/25/a.html index d6e7826aa1..ae86e58612 100644 --- a/2022/05/25/a.html +++ b/2022/05/25/a.html @@ -22,7 +22,7 @@ - + @@ -1782,8 +1782,8 @@ } - - + + + @@ -4167,7 +4167,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/06/14/a.html b/2022/06/14/a.html index ea3671cc62..8f99ed6d66 100644 --- a/2022/06/14/a.html +++ b/2022/06/14/a.html @@ -22,7 +22,7 @@ - + @@ -1782,8 +1782,8 @@ } - - + + + @@ -3761,7 +3761,7 @@

- + @@ -3891,19 +3891,19 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/06/16/a.html b/2022/06/16/a.html index e320cd076e..ab7a7a4dc6 100644 --- a/2022/06/16/a.html +++ b/2022/06/16/a.html @@ -22,7 +22,7 @@ - + @@ -1781,8 +1781,8 @@ } - - + + + @@ -3781,7 +3781,7 @@

- + @@ -3911,19 +3911,19 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/06/17/a.html b/2022/06/17/a.html index 817be4cf32..a8868ce134 100644 --- a/2022/06/17/a.html +++ b/2022/06/17/a.html @@ -22,7 +22,7 @@ - + @@ -1782,8 +1782,8 @@ } - - + + + @@ -3844,7 +3844,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/06/17/b.html b/2022/06/17/b.html index 8c7b2a6e3e..4adb830473 100644 --- a/2022/06/17/b.html +++ b/2022/06/17/b.html @@ -22,7 +22,7 @@ - + @@ -1782,8 +1782,8 @@ } - - + + + @@ -3813,7 +3813,7 @@

- + @@ -3943,19 +3943,19 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/06/20/a.html b/2022/06/20/a.html index 64bb2566a9..7b0437283e 100644 --- a/2022/06/20/a.html +++ b/2022/06/20/a.html @@ -22,7 +22,7 @@ - + @@ -1782,8 +1782,8 @@ } - - + + + @@ -3805,7 +3805,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/07/02/a.html b/2022/07/02/a.html index 7cf9e6025a..8df47eaaa9 100644 --- a/2022/07/02/a.html +++ b/2022/07/02/a.html @@ -22,7 +22,7 @@ - + @@ -1782,8 +1782,8 @@ } - - + + + @@ -3843,7 +3843,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/07/03/a.html b/2022/07/03/a.html index 67008009ca..b4dc0b1a18 100644 --- a/2022/07/03/a.html +++ b/2022/07/03/a.html @@ -22,7 +22,7 @@ - + @@ -1782,8 +1782,8 @@ } - - + + + @@ -3780,7 +3780,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/07/05/a.html b/2022/07/05/a.html index 3d0a699e8f..3ef5daafab 100644 --- a/2022/07/05/a.html +++ b/2022/07/05/a.html @@ -22,7 +22,7 @@ - + @@ -1781,8 +1781,8 @@ } - - + + + @@ -3762,7 +3762,7 @@

- + @@ -3892,19 +3892,19 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/07/13/a.html b/2022/07/13/a.html index 3d56adb97f..46985b571d 100644 --- a/2022/07/13/a.html +++ b/2022/07/13/a.html @@ -22,7 +22,7 @@ - + @@ -1782,8 +1782,8 @@ } - - + + + @@ -3791,7 +3791,7 @@

- + @@ -3921,19 +3921,19 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/09/08/a.html b/2022/09/08/a.html index 37b1ebc934..b5bd49b459 100644 --- a/2022/09/08/a.html +++ b/2022/09/08/a.html @@ -22,7 +22,7 @@ - + @@ -1783,8 +1783,8 @@ } - - + + + @@ -3789,7 +3789,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/09/09/a.html b/2022/09/09/a.html index 3e14d7e005..cda30f3692 100644 --- a/2022/09/09/a.html +++ b/2022/09/09/a.html @@ -22,7 +22,7 @@ - + @@ -1783,8 +1783,8 @@ } - - + + + @@ -3820,7 +3820,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/09/09/b.html b/2022/09/09/b.html index 050eb3842c..a4c4033298 100644 --- a/2022/09/09/b.html +++ b/2022/09/09/b.html @@ -22,7 +22,7 @@ - + @@ -1782,8 +1782,8 @@ } - - + + + @@ -3758,7 +3758,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/09/20/a.html b/2022/09/20/a.html index 99390172ca..a193559b45 100644 --- a/2022/09/20/a.html +++ b/2022/09/20/a.html @@ -22,7 +22,7 @@ - + @@ -1781,8 +1781,8 @@ } - - + + + @@ -3813,7 +3813,7 @@

- + @@ -3943,19 +3943,19 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/11/23/a.html b/2022/11/23/a.html index 4e33809a63..ad5d2dc583 100644 --- a/2022/11/23/a.html +++ b/2022/11/23/a.html @@ -22,7 +22,7 @@ - + @@ -1781,8 +1781,8 @@ } - - + + + @@ -3950,7 +3950,7 @@

- + @@ -4080,19 +4080,19 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/11/24/a.html b/2022/11/24/a.html index 9ea2dfc4ac..b317bf6ab3 100644 --- a/2022/11/24/a.html +++ b/2022/11/24/a.html @@ -22,7 +22,7 @@ - + @@ -1781,8 +1781,8 @@ } - - + + + @@ -3844,7 +3844,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/11/25/a.html b/2022/11/25/a.html index 9d1a78e857..74e4f1a7d6 100644 --- a/2022/11/25/a.html +++ b/2022/11/25/a.html @@ -22,7 +22,7 @@ - + @@ -1781,8 +1781,8 @@ } - - + + + @@ -3975,7 +3975,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/11/26/a.html b/2022/11/26/a.html index f42e90a047..97cd4058a0 100644 --- a/2022/11/26/a.html +++ b/2022/11/26/a.html @@ -22,7 +22,7 @@ - + @@ -1781,8 +1781,8 @@ } - - + + + @@ -3782,7 +3782,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/11/28/a.html b/2022/11/28/a.html index 6a433b587e..18870ea6fd 100644 --- a/2022/11/28/a.html +++ b/2022/11/28/a.html @@ -22,7 +22,7 @@ - + @@ -1781,8 +1781,8 @@ } - - + + + @@ -3812,7 +3812,7 @@

- + @@ -3942,19 +3942,19 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/11/29/a.html b/2022/11/29/a.html index a1986bed8f..26d51c2ee5 100644 --- a/2022/11/29/a.html +++ b/2022/11/29/a.html @@ -22,7 +22,7 @@ - + @@ -1781,8 +1781,8 @@ } - - + + + @@ -3850,7 +3850,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/11/29/b.html b/2022/11/29/b.html index 03a7ea3dfa..a7d6486d86 100644 --- a/2022/11/29/b.html +++ b/2022/11/29/b.html @@ -22,7 +22,7 @@ - + @@ -1781,8 +1781,8 @@ } - - + + + @@ -3906,7 +3906,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/12/10/a.html b/2022/12/10/a.html index 3780af34b8..e742722a09 100644 --- a/2022/12/10/a.html +++ b/2022/12/10/a.html @@ -22,7 +22,7 @@ - + @@ -1782,8 +1782,8 @@ } - - + + + @@ -4092,7 +4092,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/12/14/a.html b/2022/12/14/a.html index da0b7d054a..35c96ca204 100644 --- a/2022/12/14/a.html +++ b/2022/12/14/a.html @@ -22,7 +22,7 @@ - + @@ -1784,8 +1784,8 @@ } - - + + + @@ -3848,7 +3848,7 @@

- + @@ -3978,19 +3978,19 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2022/12/18/a.html b/2022/12/18/a.html index c3f00ff83e..a1b09cc0fd 100644 --- a/2022/12/18/a.html +++ b/2022/12/18/a.html @@ -22,7 +22,7 @@ - + @@ -1782,8 +1782,8 @@ } - - + + + @@ -3768,7 +3768,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2023/02/09/a.html b/2023/02/09/a.html index ecc8c8d52a..8d31a6bf06 100644 --- a/2023/02/09/a.html +++ b/2023/02/09/a.html @@ -22,7 +22,7 @@ - + @@ -1782,8 +1782,8 @@ } - - + + + @@ -3786,7 +3786,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2023/02/21/a.html b/2023/02/21/a.html index db1bab0fe8..43ae55d2a7 100644 --- a/2023/02/21/a.html +++ b/2023/02/21/a.html @@ -22,7 +22,7 @@ - + @@ -1781,8 +1781,8 @@ } - - + + + @@ -3766,7 +3766,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2023/02/23/a.html b/2023/02/23/a.html index e1347da813..ff748b499f 100644 --- a/2023/02/23/a.html +++ b/2023/02/23/a.html @@ -22,7 +22,7 @@ - + @@ -1782,8 +1782,8 @@ } - - + + + @@ -3809,7 +3809,7 @@

- + @@ -3939,19 +3939,19 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2023/02/27/a.html b/2023/02/27/a.html index abed046897..a0150b1ea4 100644 --- a/2023/02/27/a.html +++ b/2023/02/27/a.html @@ -22,7 +22,7 @@ - + @@ -1782,8 +1782,8 @@ } - - + + + @@ -3773,7 +3773,7 @@

1. R - + @@ -3903,19 +3903,19 @@

1. R const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3984,7 +3984,7 @@

1. R await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4104,7 +4104,7 @@

1. R function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4444,7 +4444,7 @@

1. R var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2023/03/01/a.html b/2023/03/01/a.html index b9d41279f9..93bca4ac5a 100644 --- a/2023/03/01/a.html +++ b/2023/03/01/a.html @@ -22,7 +22,7 @@ - + @@ -1782,8 +1782,8 @@ } - - + + + @@ -3843,7 +3843,7 @@

- + @@ -3973,19 +3973,19 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2023/03/02/a.html b/2023/03/02/a.html index cd761c8703..aaa4280926 100644 --- a/2023/03/02/a.html +++ b/2023/03/02/a.html @@ -22,7 +22,7 @@ - + @@ -1781,8 +1781,8 @@ } - - + + + @@ -3773,7 +3773,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2023/03/07/a.html b/2023/03/07/a.html index 2bf09dfcc9..e64a7c3fb5 100644 --- a/2023/03/07/a.html +++ b/2023/03/07/a.html @@ -22,7 +22,7 @@ - + @@ -1781,8 +1781,8 @@ } - - + + + @@ -3894,7 +3894,7 @@

- + @@ -4024,19 +4024,19 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2023/03/08/a.html b/2023/03/08/a.html index e955b2a0bb..217c76ba82 100644 --- a/2023/03/08/a.html +++ b/2023/03/08/a.html @@ -22,7 +22,7 @@ - + @@ -1781,8 +1781,8 @@ } - - + + + @@ -3770,7 +3770,7 @@

- + @@ -3900,19 +3900,19 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2023/03/12/a.html b/2023/03/12/a.html index 10aed1e808..c42d6a10f1 100644 --- a/2023/03/12/a.html +++ b/2023/03/12/a.html @@ -22,7 +22,7 @@ - + @@ -1782,8 +1782,8 @@ } - - + + + @@ -3779,7 +3779,7 @@

1.2 T - + @@ -3909,19 +3909,19 @@

1.2 T const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3990,7 +3990,7 @@

1.2 T await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4110,7 +4110,7 @@

1.2 T function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4450,7 +4450,7 @@

1.2 T var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2023/03/16/a.html b/2023/03/16/a.html index c0d1de0b65..1a0c3acc4c 100644 --- a/2023/03/16/a.html +++ b/2023/03/16/a.html @@ -22,7 +22,7 @@ - + @@ -1782,8 +1782,8 @@ } - - + + + @@ -3820,7 +3820,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2023/03/17/a.html b/2023/03/17/a.html index ed6b791a50..8fae120a21 100644 --- a/2023/03/17/a.html +++ b/2023/03/17/a.html @@ -22,7 +22,7 @@ - + @@ -1781,8 +1781,8 @@ } - - + + + @@ -3773,7 +3773,7 @@

- + @@ -3903,19 +3903,19 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2023/03/22/a.html b/2023/03/22/a.html index 96500b3ac4..88bb754269 100644 --- a/2023/03/22/a.html +++ b/2023/03/22/a.html @@ -22,7 +22,7 @@ - + @@ -1782,8 +1782,8 @@ } - - + + + @@ -3829,7 +3829,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2023/03/29/a.html b/2023/03/29/a.html index 07f3537bcf..d0386275e5 100644 --- a/2023/03/29/a.html +++ b/2023/03/29/a.html @@ -22,7 +22,7 @@ - + @@ -1782,8 +1782,8 @@ } - - + + + @@ -3785,7 +3785,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2023/04/03/a.html b/2023/04/03/a.html index 36ce841515..56e24bddf1 100644 --- a/2023/04/03/a.html +++ b/2023/04/03/a.html @@ -22,7 +22,7 @@ - + @@ -1781,8 +1781,8 @@ } - - + + + @@ -3855,7 +3855,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2023/04/11/a.html b/2023/04/11/a.html index ed77571e88..54f5af4aea 100644 --- a/2023/04/11/a.html +++ b/2023/04/11/a.html @@ -22,7 +22,7 @@ - + @@ -1781,8 +1781,8 @@ } - - + + + @@ -3785,7 +3785,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2023/04/20/a.html b/2023/04/20/a.html index 5b35b1579b..6e195a3421 100644 --- a/2023/04/20/a.html +++ b/2023/04/20/a.html @@ -22,7 +22,7 @@ - + @@ -1782,8 +1782,8 @@ } - - + + + @@ -3853,7 +3853,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2023/06/13/a.html b/2023/06/13/a.html index 5376460155..609d46b2cd 100644 --- a/2023/06/13/a.html +++ b/2023/06/13/a.html @@ -22,7 +22,7 @@ - + @@ -1781,8 +1781,8 @@ } - - + + + @@ -3816,7 +3816,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2023/06/15/a.html b/2023/06/15/a.html index 750862aba3..24669d0177 100644 --- a/2023/06/15/a.html +++ b/2023/06/15/a.html @@ -22,7 +22,7 @@ - + @@ -1783,8 +1783,8 @@ } - - + + + @@ -3846,7 +3846,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2023/06/16/a.html b/2023/06/16/a.html index 70debe8f8a..f7b47fa7a5 100644 --- a/2023/06/16/a.html +++ b/2023/06/16/a.html @@ -22,7 +22,7 @@ - + @@ -1781,8 +1781,8 @@ } - - + + + @@ -3921,7 +3921,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2023/06/20/a.html b/2023/06/20/a.html index 56ce7d5326..cd9188079f 100644 --- a/2023/06/20/a.html +++ b/2023/06/20/a.html @@ -22,7 +22,7 @@ - + @@ -1783,8 +1783,8 @@ } - - + + + @@ -3818,7 +3818,7 @@

- + @@ -3948,19 +3948,19 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2023/07/04/a.html b/2023/07/04/a.html index e5547ad679..604ea561d1 100644 --- a/2023/07/04/a.html +++ b/2023/07/04/a.html @@ -22,7 +22,7 @@ - + @@ -1781,8 +1781,8 @@ } - - + + + @@ -3837,7 +3837,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2023/07/11/a.html b/2023/07/11/a.html index d015385440..f15da2e786 100644 --- a/2023/07/11/a.html +++ b/2023/07/11/a.html @@ -22,7 +22,7 @@ - + @@ -1782,8 +1782,8 @@ } - - + + + @@ -3803,7 +3803,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2023/07/13/a.html b/2023/07/13/a.html index b67f31c690..f0f1d8592d 100644 --- a/2023/07/13/a.html +++ b/2023/07/13/a.html @@ -22,7 +22,7 @@ - + @@ -1783,8 +1783,8 @@ } - - + + + @@ -3850,7 +3850,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2023/08/04/a.html b/2023/08/04/a.html index 898f5a1e39..1d22f4e30f 100644 --- a/2023/08/04/a.html +++ b/2023/08/04/a.html @@ -22,7 +22,7 @@ - + @@ -1782,8 +1782,8 @@ } - - + + + @@ -3757,7 +3762,7 @@

{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/2023/08/18/a.html b/2023/08/18/a.html new file mode 100644 index 0000000000..4cc8d00f78 --- /dev/null +++ b/2023/08/18/a.html @@ -0,0 +1,4877 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + ATAC-seq和CUT&Tag技术原理和实验流程 - 我的小破站 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + +
+ +
+
+ +
+
+ + + + + + + + + + + + +
+
+

前阵子去兰州开全国植物生物学大会,做实验的师弟问了我一个问题:什么是ATAC-seq?因为王茂军老师课题组有同学在做CUT&Tag,我脑海里第一个出现的也是CUT&Tag,两者的研究内容虽然不同,但是使用的分析方式是非常相似的。正好看到菲沙基因做过这两个技术的讲座,这篇博客就整理一下介绍这两个技术以及衍生技术的分析原理,加深下自己的理解,详细的分析流程留到以后需要做的时候再记录。

+ + +

1. ATAC-Seq

ATAC-seq全称Assay for Transposase Accessible Chromatin with high-throughput sequencing,翻译为转座酶可及染色质的高通量测序分析,简单来说这个技术是运用转座酶获取开放染色质区,然后通过高通量测序技术和生物信息学挖掘相关的基因信息,解决生物学相关问题。

+

1.1 背景介绍

什么是开放染色质?

在前面介绍三维基因组的博客中,介绍了真核生物的染色质结构由低级到高级可以分为4种,染色体的基本结构单位是核小体,核小体串珠结构螺旋化(也就是不断地压缩折叠)形成了直径为30nm的染色质纤维,细胞核内大多数染色质都是以这种染色质纤维的形式存在的。

+

+

我们知道DNA的复制和转录过程需要将染色质紧密的结构打开(打开的过程与组蛋白乙酰化有密切联系,组蛋白乙酰化使组蛋白携带的正电荷减少,削弱了组蛋白与DNA结合的能力,从而使染色质区域的结构从紧密变得松散),这部分打开后结构疏松的染色质就是开放染色质(open chromatin),当染色质打开后,暴露的DNA序列就有足够的空间和转录因子(Transcription factors,TF)结合,进而调控基因的表达。

+

这种允许顺式调控元件和反式作用因子结合,也就是允许与染色质进行物理接触的程度就是染色质的可接近性(chromatin accessibility),也称为染色质可及性

+

如何检测染色质开放区?

简单说一下几个常用的检测染色质开放区的方法。

+

一种是传统的使用DNA酶的实验方法MNase-seqDnase-seq。两者的思路都是将开放染色质区的DNA用DNA酶酶切后进行高通量测序。前者用的是限制性外切酶,将不受核小体保护的区域切除,只留下核小体上缠绕的DNA序列;后者用的限制性内切酶,将受核小体保护的区域切除,留下核小体之间的序列。

+

另一种是基于酚氯仿抽提的技术FAIRE-seq技术,超声波破碎甲醛固定的染色质,酚氯仿抽提得到的上层水相认为是潜在的开放染色质区,针对抽提得到的片段化开放性染色质区进行建库测序。

+

还有一种就是经典的研究蛋白与DNA互作的ChIP-seq技术,因为需要制作对应的转录因子的抗体去拉DNA,所以该技术只能根据明确的转录因子来检测该转录因子与DNA的互作,局限性比较大,这里就提一下不放在一起比较了。

+

最后就是ATAC-seq技术,依赖改造的Tn5转座酶(转座DNA设计为测序接头)将测序接头引入染色质开放区,对酶切后的DNA片段进行富集,最后通过PCR扩增后进行高通量测序。另一方面,转座酶还可以切割开放区染色质附近的核小体间连接区DNA,简单来说可以得到MNase-seq和Dnase-seq两种技术的结果。

+

如下图所示,我们可以比较一下这几种检测染色质开放区技术的检测范围和灵敏度:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
研究方法细胞数量获取方式优点缺点
DNase-seq1*10^7DNase Ⅰ切割不受保护的DNA序列单碱基对的酶切位点分辨率酶用量要准确控制,切割有偏好性
MNase-seq1*10^7MNase优先切割受保护的DNA序列酶切特性有较高分辨率酶用量要准确控制,切割有偏好性
FAIRE-seq1*10^5~1*10^7超声波打断染色质无偏性分析,重复性好信噪比低,数据解读困难
ATAC-seq5*10^2~5*10^4改造的Tn5转座酶切割开放区并插入测序接头细胞需求量少,实验时间短,灵敏度高,重复性好容易引入线粒体污染
+

1.2 实验流程

整个实验流程可以分为6个步骤:

+
+
    +
  1. 细胞悬液制备(500~50000个细胞,最难的一步)
  2. +
  3. 细胞裂解,制备细胞核(完整性十分重要)
  4. +
  5. Tn5酶切,37℃酶切孵育
  6. +
  7. DNA片段纯化,磁珠法回收
  8. +
  9. PCR扩增12-15个循环
  10. +
  11. 上机测序
  12. +
+
+ + +

上面PCR扩增这一步的流程,可以看到Tn5酶切过程中会在上下游分别产生一个缺口,因此需要72℃延伸的过程来补平缺口。引入barcoded primer是为了区分不同的样品(单细胞测序的话是区分不同类型细胞)。

+

从上面的实验流程也可以看出,ATAC-seq中比较重要的步骤是细胞悬液制备和提取完整的细胞核,在细胞裂解和提取细胞核过程中,线粒体DNA可能会与染色体DNA一起被提取和处理,从而引入线粒体污染。线粒体是没有组蛋白保护的,容易被Tn5转座酶切割,同时线粒体的拷贝数也比染色体高很多,如果线粒体在实验过程中没有去除,很容易导致线粒体DNA被富集,影响染色体DNA测序深度和覆盖度。

+

1.3 生信分析

获得下机数据后,就可以开始做上下游的生信分析。因为我自己没跑过这个流程,所以这里以菲沙基因提供的流程为例,着重介绍ATAC-seq流程中产生的图如何解读,以及我们可以做哪些下游分析。

+

+

下机数据预处理

拿到下机数据后首先去接头(adapter trimming),然后比对到参考基因组(alignment),对比对后得到的bam文件进行过滤,具体而言是提取可靠比对、去除PCR重复(推荐用picard软件)、去除细胞器污染(主要是线粒体和叶绿体)这三步。这些处理方式都是老朋友了。

+

数据质量评估

ATAC-seq数据质量评估主要是看两个图,一个是插入片段分布图(Fragment Insertion Size Distribution),一个是TSS富集峰图

+

插入片段分布图

+

+

ATAC-seq的插入片段分布有着非常鲜明的特点,一般把<100 bp的片段区域称NFR(Nucleosome-Free Region)也就是无核小体区,这部分区域也是转座酶最容易切割的区域,每隔10.5 bp就有一个小齿,对应DNA螺旋一周的间距。200 bp有一个峰对应的是核小体单体的插入片段长度,再远点的400 bp和600 bp有两个小峰,对应核小体二聚体和核小体三聚体的插入片段长度。

+

TSS富集峰图

+ + +

转录起始位点(Transcription Start Site,TSS)是没有核小体的,所以在ATAC-seq质控分析中,可以明显看到NFR在转录起始位点富集。以上图为例,我们选择TSS上下游3Kb的区域,NFR reads在TSS位点两侧有明显富集趋势。底下的热图也是同样的意思,每一行表示一个基因或者转录本,图中的红色区域也不一定要延伸到底,因为部分TSS可能没有在这个时期开放,这是很正常的现象。

+

这两个质控步骤可以先做第一个,第二个TSS富集峰图需要在peak calling和转换文件格式为.bw之后,使用Deeptools工具作图。

+

Reads Shifting

质控后还有一个步骤是进行reads shifting,前面说过Tn5酶切过程中会在上下游产生一个缺口,因此需要将正链正向移动4bp,负链负向移动5bp。

+

这一步在ACAT-seq的原文中有做,不做的话对单碱基分辨率要求比较高的分析是有影响的(比如转录因子足迹分析,下面的Motif Analysis会说)。

+

Peak Calling

peak calling是后续所有分析的基础。简单来说,在将reads比对到参考基因组后,因为进行的是pair-end测序,一对reads之间的序列为一个fragment,统计每个碱基上fragment的数量作图,哪个地方fragment数量多,在统计图上就会显示出一个峰,也就是一个peak。peak calling的过程就是检测染色质开放区的fragment富集信号。

+ + +

这一步用的软件有比较经典的MACS2,这个软件可以处理ChIP-seq、ATAC-seq、CUT&TAG等等的数据,需要调整不同的参数。与ChIP-seq不同,ATAC-seq的Tn5转座酶酶切的是染色质开放区域,在TF结合区域的DNA是拉不下来的(反映在峰图上是一个谷,ChIP-seq是一个峰),因此在调整峰值偏移(peak shift)的时候,一般用shift-extend的方法进行分析,ATAC-seq需要向外shift。如下图:

+ + +

比如我们测序reads长度是150bp,两条reads的5‘端代表Tn5的酶切位点,我们需要向外shift 75bp,让酶切位点处于reads的中间位置,再进行peak calling。

+

用上面的软件进行peak calling后会生成bedGraph文件,也就是.bdg后缀的文件,是bed文件的一种扩展,可以在IGV基因组浏览器中打开(可能会比较卡),也可以借助UCSC的工具bedGraphToBigWig转成BigWig文件后(.bw后缀)再到IGV基因组浏览器中打开。还有一个重要的结果文件是.narrowPeak后缀的文件,也可以直接导入IGV。

+

+

以上上游分析的步骤可以参考ATAC-seq data analysis: from FASTQ to peaks | Yiwei Niu’s Note

+

接下来是常见的一些下游分析的方法。

+

IDR Peak

对于一个样本如果做了多个重复,就需要对样本的可重复性进行评估。可以用ENCODE项目的一个软件包Irreproducible Discovery Rate (IDR) ,导入两个样本的.narrowPeak结果文件后作图分析,这个软件的作用是评估重复样本间peak的一致性,生成的图如下:

+

+

IDR算法同时考虑了peaks间的overlap和富集倍数的一致性。上面的图所有的点都是两个样本间相互overlap的peak,也就是都是可重复的,红点代表富集倍数是有差别的,黑点代表富集倍数是一致的,因此黑点数量越多越好。

+

Peak Annotation

拿到peak后,如果想要知道这些peak在基因组的哪些地方分布功能是什么,就需要对peak进行注释,常用的有R包ChIPseeker

+

Motif Analysis

这一部分能做的分析还是相当多的,列举几个:

+
+
    +
  • 从头预测:预测新的motif,注释已存在的motif。软件:MEME+Tomtom
  • +
  • Motif扫描:除了开放染色质区域,寻找其他序列所有motif的位置信息。软件:FIMO
  • +
  • 转录因子富集:软件AME或者HOMER
  • +
  • 转录因子足迹(TF FootPrint):转录因子占位效应(转录因子结合在DNA上,阻止了Tn5酶切,在开放染色质区域留下一个缺失的位置),注意要进行前面说的reads shifting。软件R/centipade
  • +
+
+

常用的motif数据库:

+
+ +
+

Nucleosome positioning

核小体定位,从前面的插入片段分布图可以看出,在ATAT-seq文库中,核小体单体插入片段数量相比NFR明显少很多,但是也有一些软件比如NuleoATACHMMRATAC可以用于核小体的占位分析。

+

联合分析

    +
  • ChIP-seq:由于转录因子的结合区域在染色质开放区,因此ATAC-seq的peak和ChIP-seq的peak之间存在部分重叠,因此这两个组学联用可以相互验证,而转录因子在ChIP-seq中独有的Peak则暗示这个转录因子可能是结合在异染色质区域的驱动型转录因子(Pioneer TFs)。对于组蛋白修饰的ChIP-seq而言,前面也说过组蛋白乙酰化与染色质开放区形成有重要联系,同样可以与ATAC-seq进行联合分析。
  • +
  • RNA-seq:比如将ATAC-seq的信号在gene body上的分布做比较,或者按照基因不同的表达量做分类,再与ATAC-seq联用分别统计不同表达量基因的TSS上的富集信号,研究ATAC-seq的富集信号是否与基因表达量相关、差异表达的基因是否受染色质可及性的调控等等。
  • +
+

2. CUT&Tag

CUT&Tag全称Cleavage Under Target & Tagmentation,翻译为靶向剪切及转座酶技术,是一种研究蛋白-DNA互作的技术,替代传统的ChIP-seq方法。开头介绍ATAC-seq和CUT&Tag非常相似,原因就在于CUT&Tag也用Tn5转座酶,只不过这个酶是Protein A/G融合的Tn5转座酶。当然,两者研究内容还是不一样的,下面详细说一下。

+

2.1 背景介绍

ChIP

前面介绍ATAC-seq的时候已经拿ChIP-seq做过对比,这里为了引出CUT&Tag还是对ChIP技术做个简单介绍。ChIP全称Chromatin Immunoprecipitation,翻译为染色质免疫共沉淀,顾名思义,这个技术有个鲜明的特征就是抗原抗体免疫反应。

+

下面是ChIP-seq的经典流程:

+ + +

我们知道,抗原抗体反应具有专一性,所以我们制备特定的转录因子的抗体(一般是多克隆抗体),将细胞核内的染色质用甲醛交联固定后进行超声破碎,这个时候与转录因子结合的DNA序列不会被打断。加入抗体做免疫共沉淀,解交联后获得与转录因子结合的DNA序列,建库测序就可以做后面的分析。

+

ChIP技术分类

    +
  • N-ChIP:用的是Native Chromatin,原生态染色质,DNA片段化的方法为酶切,用上面提到的MNase。只能处理结合能力比较强的蛋白(一般用于组蛋白修饰),蛋白复合体存在解离的风险,实验难度相对较大。
  • +
  • X-ChIP:用的是Cross-linked Chromatin,甲醛交联的染色质,DNA片段化方式为超声波。甲醛交联的背景一般比较高,抗体识别位点可能会被屏蔽。
  • +
+

ChIP-seq技术非常依赖于抗体质量,如果研究低表达的蛋白,制备抗体的也是很大的挑战。近年在鉴定转录因子结合位点上又出了个新技术DAP-Seq,通过体外蛋白表达技术,表达出带有Halo标签的转录因子蛋白。通过Halo标签的抗体富集对应的蛋白DNA复合物,从而使所有蛋白都可以被一种抗体(Halo标签抗体)富集,绕过了抗体制备的难题(和后面要说的没啥关系,写到技术分类这里提一嘴)。

+

CUT系列技术

    +
  • CUT&RUN:Cleavage Under Targets and Release Using Nuclease,靶向剪切和核酸酶释放,这里用的核酸酶是与Protein A/G融合的MNase。
  • +
  • CUT&Tag:Cleavage Under Targets and Tagmentation,靶向剪切和转座酶,这里用的酶是Protein A/G融合的Tn5转座酶,是CUT&RUN技术的改进版。
  • +
+

两种技术用的酶不一样,Tn5转座酶可以插入测序标签,因此CUT&Tag比CUT&RUN节省了更多建库时间,1天就可以完成建库。

+

2.2 实验流程

整个实验流程可以分为6步:

+
+
    +
  1. 收集细胞,刀豆蛋白的磁珠吸附细胞膜
  2. +
  3. 细胞电穿孔后分别孵一抗和二抗,一抗结合目标蛋白,二抗放大信号
  4. +
  5. Hyperactive pA/pG-Tn5 Transposon结合,这种改造的Tn5转座酶会特异性识别抗体并结合
  6. +
  7. 加入镁离子或者钙离子激活Tn5转座酶,片段化DNA
  8. +
  9. 提取DNA
  10. +
  11. PCR扩增文库
  12. +
+
+ + +

上面的流程图可以看出,CUT&Tag与ATAC-seq的区别就在于,CUT&Tag用了抗体,Tn5转座酶进一步改造可以识别抗体。

+

因此CUT&Tag实验需要做阴性对照(IgG),CUT&Tag文库和ATCA文库差别就在于抗体识别的蛋白,如果抗体的特异性很差,比如在所有组蛋白上都有结合,那这个时候建库得到的就是ATCA文库而不是CUT&Tag文库。这个时候阴性对照就很重要了,如果在IgG上也能出正常的PCR结果,就说明抗体有问题或者实验设计有问题了。

+

因为CUT&Tag用的是Tn5转座酶,所以也有一个缺陷,比如要研究异染色质的蛋白与DNA互作就做不了,因为Tn5转座酶无法在异染色质区剪切DNA。或者你要研究的蛋白空间结构比较大,导致Tn5转座酶无法剪切到DNA,这个时候也不能用CUT&Tag。

+

2.3 生信分析

CUT&Tag生信分析和ATAC-seq几乎一模一样,下游分析也可以做IDR Peak、Peak Annotation和Motif Analysis,这里就不赘述了。

+ +
+
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+ +

欢迎小伙伴们留言评论~

+ + +
+ +
+ + + + + + +
+ + + + + + + + + + +
+ + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + diff --git a/404.html b/404.html index 4cdaf5472e..5d1396cc25 100644 --- a/404.html +++ b/404.html @@ -22,7 +22,7 @@ - + @@ -1780,8 +1780,8 @@ } - - + + + @@ -3521,7 +3521,7 @@ - + @@ -3651,19 +3651,19 @@ const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3732,7 +3732,7 @@ await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -3852,7 +3852,7 @@ function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4192,7 +4192,7 @@ var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/AI/index.html b/AI/index.html index 5dbae170ac..101183a20a 100644 --- a/AI/index.html +++ b/AI/index.html @@ -22,7 +22,7 @@ - + @@ -1780,8 +1780,8 @@ } - - + + + @@ -3892,7 +3897,7 @@

Phantom

- + @@ -4022,19 +4027,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4103,7 +4108,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4223,7 +4228,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4563,7 +4568,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/Bioinformatics/index.html b/Bioinformatics/index.html index a736bf405e..2c2e1187c7 100644 --- a/Bioinformatics/index.html +++ b/Bioinformatics/index.html @@ -22,7 +22,7 @@ - + @@ -1780,8 +1780,8 @@ } - - + + + @@ -7452,7 +7457,7 @@

Phantom

- + @@ -7582,19 +7587,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -7663,7 +7668,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -7783,7 +7788,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -8123,7 +8128,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/about/index.html b/about/index.html index f65fa36164..74651a3390 100644 --- a/about/index.html +++ b/about/index.html @@ -22,7 +22,7 @@ - + @@ -1780,8 +1780,8 @@ } - - + + + @@ -3519,7 +3519,7 @@ - + @@ -3649,19 +3649,19 @@ const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3730,7 +3730,7 @@ await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -3850,7 +3850,7 @@ function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4190,7 +4190,7 @@ var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/archives/2022/04/index.html b/archives/2022/04/index.html index 5c4028a0fa..1adff911ef 100644 --- a/archives/2022/04/index.html +++ b/archives/2022/04/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4354,7 +4359,7 @@

Phantom

- + @@ -4484,19 +4489,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4565,7 +4570,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4685,7 +4690,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -5025,7 +5030,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/archives/2022/04/page/2/index.html b/archives/2022/04/page/2/index.html index f8dd50fbcf..51d7859af2 100644 --- a/archives/2022/04/page/2/index.html +++ b/archives/2022/04/page/2/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3818,7 +3823,7 @@

Phantom

- + @@ -3948,19 +3953,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4029,7 +4034,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4149,7 +4154,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4489,7 +4494,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/archives/2022/05/index.html b/archives/2022/05/index.html index 48b47a0d29..7e232ebef2 100644 --- a/archives/2022/05/index.html +++ b/archives/2022/05/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4067,7 +4072,7 @@

Phantom

- + @@ -4197,19 +4202,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4278,7 +4283,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4398,7 +4403,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4738,7 +4743,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/archives/2022/06/index.html b/archives/2022/06/index.html index 0f5ca5c1dd..66cc509f89 100644 --- a/archives/2022/06/index.html +++ b/archives/2022/06/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4008,7 +4013,7 @@

Phantom

- + @@ -4138,19 +4143,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4219,7 +4224,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4339,7 +4344,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4679,7 +4684,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/archives/2022/07/index.html b/archives/2022/07/index.html index da31528d09..665058fad9 100644 --- a/archives/2022/07/index.html +++ b/archives/2022/07/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3941,7 +3946,7 @@

Phantom

- + @@ -4071,19 +4076,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4152,7 +4157,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4272,7 +4277,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4612,7 +4617,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/archives/2022/09/index.html b/archives/2022/09/index.html index af37b72093..112449b9e1 100644 --- a/archives/2022/09/index.html +++ b/archives/2022/09/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3933,7 +3938,7 @@

Phantom

- + @@ -4063,19 +4068,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4144,7 +4149,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4264,7 +4269,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4604,7 +4609,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/archives/2022/11/index.html b/archives/2022/11/index.html index 6f54746baa..1ed6da5b08 100644 --- a/archives/2022/11/index.html +++ b/archives/2022/11/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4150,7 +4155,7 @@

Phantom

- + @@ -4280,19 +4285,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4361,7 +4366,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4481,7 +4486,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4821,7 +4826,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/archives/2022/12/index.html b/archives/2022/12/index.html index 63da98ffda..54074e1b07 100644 --- a/archives/2022/12/index.html +++ b/archives/2022/12/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3870,7 +3875,7 @@

Phantom

- + @@ -4000,19 +4005,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4081,7 +4086,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4201,7 +4206,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4541,7 +4546,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/archives/2022/index.html b/archives/2022/index.html index 23d44f2a95..bde4d5d4b8 100644 --- a/archives/2022/index.html +++ b/archives/2022/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4354,7 +4359,7 @@

Phantom

- + @@ -4484,19 +4489,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4565,7 +4570,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4685,7 +4690,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -5025,7 +5030,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/archives/2022/page/2/index.html b/archives/2022/page/2/index.html index 2951204323..51863d579d 100644 --- a/archives/2022/page/2/index.html +++ b/archives/2022/page/2/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4324,7 +4329,7 @@

Phantom

- + @@ -4454,19 +4459,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4535,7 +4540,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4655,7 +4660,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4995,7 +5000,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/archives/2022/page/3/index.html b/archives/2022/page/3/index.html index 83e548b4fa..a8bc0d6f8e 100644 --- a/archives/2022/page/3/index.html +++ b/archives/2022/page/3/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4320,7 +4325,7 @@

Phantom

- + @@ -4450,19 +4455,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4531,7 +4536,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4651,7 +4656,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4991,7 +4996,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/archives/2022/page/4/index.html b/archives/2022/page/4/index.html index ce85949f41..547e0e6536 100644 --- a/archives/2022/page/4/index.html +++ b/archives/2022/page/4/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4340,7 +4345,7 @@

Phantom

- + @@ -4470,19 +4475,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4551,7 +4556,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4671,7 +4676,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -5011,7 +5016,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/archives/2022/page/5/index.html b/archives/2022/page/5/index.html index 442041d9b7..7c9fff335d 100644 --- a/archives/2022/page/5/index.html +++ b/archives/2022/page/5/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3753,7 +3758,7 @@

Phantom

- + @@ -3883,19 +3888,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3964,7 +3969,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4084,7 +4089,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4424,7 +4429,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/archives/2023/02/index.html b/archives/2023/02/index.html index b63150bd26..b588c25164 100644 --- a/archives/2023/02/index.html +++ b/archives/2023/02/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3941,7 +3946,7 @@

Phantom

- + @@ -4071,19 +4076,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4152,7 +4157,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4272,7 +4277,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4612,7 +4617,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/archives/2023/03/index.html b/archives/2023/03/index.html index 3264b64f5d..398be119f9 100644 --- a/archives/2023/03/index.html +++ b/archives/2023/03/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4277,7 +4282,7 @@

Phantom

- + @@ -4407,19 +4412,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4488,7 +4493,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4608,7 +4613,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4948,7 +4953,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/archives/2023/04/index.html b/archives/2023/04/index.html index 06ebd62abb..d019c526df 100644 --- a/archives/2023/04/index.html +++ b/archives/2023/04/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3882,7 +3887,7 @@

Phantom

- + @@ -4012,19 +4017,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4093,7 +4098,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4213,7 +4218,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4553,7 +4558,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/archives/2023/06/index.html b/archives/2023/06/index.html index 4847eafdae..4d06f629a4 100644 --- a/archives/2023/06/index.html +++ b/archives/2023/06/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3950,7 +3955,7 @@

Phantom

- + @@ -4080,19 +4085,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4161,7 +4166,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4281,7 +4286,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4621,7 +4626,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/archives/2023/07/index.html b/archives/2023/07/index.html index e054e7d731..4bdedd631b 100644 --- a/archives/2023/07/index.html +++ b/archives/2023/07/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3882,7 +3887,7 @@

Phantom

- + @@ -4012,19 +4017,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4093,7 +4098,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4213,7 +4218,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4553,7 +4558,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/archives/2023/08/index.html b/archives/2023/08/index.html index 1193cf5f4d..23bb142b18 100644 --- a/archives/2023/08/index.html +++ b/archives/2023/08/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3744,7 +3816,7 @@

Phantom

- + @@ -3874,19 +3946,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3955,7 +4027,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4075,7 +4147,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4415,7 +4487,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/archives/2023/index.html b/archives/2023/index.html index fa7964da67..dc84406342 100644 --- a/archives/2023/index.html +++ b/archives/2023/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4363,7 +4368,7 @@

Phantom

- + @@ -4493,19 +4498,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4574,7 +4579,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4694,7 +4699,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -5034,7 +5039,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/archives/2023/page/2/index.html b/archives/2023/page/2/index.html index b34c6f3182..e7b50f0f74 100644 --- a/archives/2023/page/2/index.html +++ b/archives/2023/page/2/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4341,7 +4346,7 @@

Phantom

- + @@ -4471,19 +4476,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4552,7 +4557,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4672,7 +4677,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -5012,7 +5017,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/archives/2023/page/3/index.html b/archives/2023/page/3/index.html index 5f679a0769..47b92c2051 100644 --- a/archives/2023/page/3/index.html +++ b/archives/2023/page/3/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3944,7 +4014,7 @@

Phantom

- + @@ -4074,19 +4144,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4155,7 +4225,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4275,7 +4345,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4615,7 +4685,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/archives/index.html b/archives/index.html index 044dd6a2ec..b422b4d0da 100644 --- a/archives/index.html +++ b/archives/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4715,7 +4736,7 @@

Phantom

- + @@ -4845,19 +4866,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4926,7 +4947,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -5046,7 +5067,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -5386,7 +5407,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/archives/page/2/index.html b/archives/page/2/index.html index 3ca0902412..4d083e04ba 100644 --- a/archives/page/2/index.html +++ b/archives/page/2/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4715,7 +4736,7 @@

Phantom

- + @@ -4845,19 +4866,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4926,7 +4947,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -5046,7 +5067,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -5386,7 +5407,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/archives/page/3/index.html b/archives/page/3/index.html index 737b3d2d3a..8bcb027e9d 100644 --- a/archives/page/3/index.html +++ b/archives/page/3/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4715,7 +4736,7 @@

Phantom

- + @@ -4845,19 +4866,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4926,7 +4947,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -5046,7 +5067,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -5386,7 +5407,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/archives/page/4/index.html b/archives/page/4/index.html index 9fff29ba9b..4aee779407 100644 --- a/archives/page/4/index.html +++ b/archives/page/4/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4715,7 +4736,7 @@

Phantom

- + @@ -4845,19 +4866,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4926,7 +4947,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -5046,7 +5067,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -5386,7 +5407,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/archives/page/5/index.html b/archives/page/5/index.html index 89c75e20b5..34b702075a 100644 --- a/archives/page/5/index.html +++ b/archives/page/5/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4715,7 +4736,7 @@

Phantom

- + @@ -4845,19 +4866,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4926,7 +4947,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -5046,7 +5067,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -5386,7 +5407,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/archives/page/6/index.html b/archives/page/6/index.html index 9fd0ad6260..e4ba1ba3cc 100644 --- a/archives/page/6/index.html +++ b/archives/page/6/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4715,7 +4736,7 @@

Phantom

- + @@ -4845,19 +4866,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4926,7 +4947,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -5046,7 +5067,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -5386,7 +5407,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/archives/page/7/index.html b/archives/page/7/index.html index 04120c219f..4f15496603 100644 --- a/archives/page/7/index.html +++ b/archives/page/7/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4715,7 +4736,7 @@

Phantom

- + @@ -4845,19 +4866,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4926,7 +4947,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -5046,7 +5067,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -5386,7 +5407,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/atom.xml b/atom.xml index eed475e863..2610cbb66a 100644 --- a/atom.xml +++ b/atom.xml @@ -6,11 +6,36 @@ - 2023-08-04T15:01:30.000Z + 2023-08-17T16:24:01.000Z http://www.shelven.com/ Hexo + + ATAC-seq和CUT&Tag技术原理和实验流程 + + http://www.shelven.com/2023/08/18/a.html + 2023-08-17T16:21:11.000Z + 2023-08-17T16:24:01.000Z + + 前阵子去兰州开全国植物生物学大会,做实验的师弟问了我一个问题:什么是ATAC-seq?因为王茂军老师课题组有同学在做CUT&Tag,我脑海里第一个出现的也是CUT&Tag,两者的研究内容虽然不同,但是使用的分析方式是非常相似的。正好看到菲沙基因做过这两个技术的讲座,这篇博客就整理一下介绍这两个技术以及衍生技术的分析原理,加深下自己的理解,详细的分析流程留到以后需要做的时候再记录。

1. ATAC-Seq

ATAC-seq全称Assay for Transposase Accessible Chromatin with high-throughput sequencing,翻译为转座酶可及染色质的高通量测序分析,简单来说这个技术是运用转座酶获取开放染色质区,然后通过高通量测序技术和生物信息学挖掘相关的基因信息,解决生物学相关问题。

1.1 背景介绍

什么是开放染色质?

在前面介绍三维基因组的博客中,介绍了真核生物的染色质结构由低级到高级可以分为4种,染色体的基本结构单位是核小体,核小体串珠结构螺旋化(也就是不断地压缩折叠)形成了直径为30nm的染色质纤维,细胞核内大多数染色质都是以这种染色质纤维的形式存在的。

我们知道DNA的复制和转录过程需要将染色质紧密的结构打开(打开的过程与组蛋白乙酰化有密切联系,组蛋白乙酰化使组蛋白携带的正电荷减少,削弱了组蛋白与DNA结合的能力,从而使染色质区域的结构从紧密变得松散),这部分打开后结构疏松的染色质就是开放染色质(open chromatin),当染色质打开后,暴露的DNA序列就有足够的空间和转录因子(Transcription factors,TF)结合,进而调控基因的表达。

这种允许顺式调控元件和反式作用因子结合,也就是允许与染色质进行物理接触的程度就是染色质的可接近性(chromatin accessibility),也称为染色质可及性

如何检测染色质开放区?

简单说一下几个常用的检测染色质开放区的方法。

一种是传统的使用DNA酶的实验方法MNase-seqDnase-seq。两者的思路都是将开放染色质区的DNA用DNA酶酶切后进行高通量测序。前者用的是限制性外切酶,将不受核小体保护的区域切除,只留下核小体上缠绕的DNA序列;后者用的限制性内切酶,将受核小体保护的区域切除,留下核小体之间的序列。

另一种是基于酚氯仿抽提的技术FAIRE-seq技术,超声波破碎甲醛固定的染色质,酚氯仿抽提得到的上层水相认为是潜在的开放染色质区,针对抽提得到的片段化开放性染色质区进行建库测序。

还有一种就是经典的研究蛋白与DNA互作的ChIP-seq技术,因为需要制作对应的转录因子的抗体去拉DNA,所以该技术只能根据明确的转录因子来检测该转录因子与DNA的互作,局限性比较大,这里就提一下不放在一起比较了。

最后就是ATAC-seq技术,依赖改造的Tn5转座酶(转座DNA设计为测序接头)将测序接头引入染色质开放区,对酶切后的DNA片段进行富集,最后通过PCR扩增后进行高通量测序。另一方面,转座酶还可以切割开放区染色质附近的核小体间连接区DNA,简单来说可以得到MNase-seq和Dnase-seq两种技术的结果。

如下图所示,我们可以比较一下这几种检测染色质开放区技术的检测范围和灵敏度:

研究方法细胞数量获取方式优点缺点
DNase-seq1*10^7DNase Ⅰ切割不受保护的DNA序列单碱基对的酶切位点分辨率酶用量要准确控制,切割有偏好性
MNase-seq1*10^7MNase优先切割受保护的DNA序列酶切特性有较高分辨率酶用量要准确控制,切割有偏好性
FAIRE-seq1*10^5~1*10^7超声波打断染色质无偏性分析,重复性好信噪比低,数据解读困难
ATAC-seq5*10^2~5*10^4改造的Tn5转座酶切割开放区并插入测序接头细胞需求量少,实验时间短,灵敏度高,重复性好容易引入线粒体污染

1.2 实验流程

整个实验流程可以分为6个步骤:

  1. 细胞悬液制备(500~50000个细胞,最难的一步)
  2. 细胞裂解,制备细胞核(完整性十分重要)
  3. Tn5酶切,37℃酶切孵育
  4. DNA片段纯化,磁珠法回收
  5. PCR扩增12-15个循环
  6. 上机测序

上面PCR扩增这一步的流程,可以看到Tn5酶切过程中会在上下游分别产生一个缺口,因此需要72℃延伸的过程来补平缺口。引入barcoded primer是为了区分不同的样品(单细胞测序的话是区分不同类型细胞)。

从上面的实验流程也可以看出,ATAC-seq中比较重要的步骤是细胞悬液制备和提取完整的细胞核,在细胞裂解和提取细胞核过程中,线粒体DNA可能会与染色体DNA一起被提取和处理,从而引入线粒体污染。线粒体是没有组蛋白保护的,容易被Tn5转座酶切割,同时线粒体的拷贝数也比染色体高很多,如果线粒体在实验过程中没有去除,很容易导致线粒体DNA被富集,影响染色体DNA测序深度和覆盖度。

1.3 生信分析

获得下机数据后,就可以开始做上下游的生信分析。因为我自己没跑过这个流程,所以这里以菲沙基因提供的流程为例,着重介绍ATAC-seq流程中产生的图如何解读,以及我们可以做哪些下游分析。

下机数据预处理

拿到下机数据后首先去接头(adapter trimming),然后比对到参考基因组(alignment),对比对后得到的bam文件进行过滤,具体而言是提取可靠比对、去除PCR重复(推荐用picard软件)、去除细胞器污染(主要是线粒体和叶绿体)这三步。这些处理方式都是老朋友了。

数据质量评估

ATAC-seq数据质量评估主要是看两个图,一个是插入片段分布图(Fragment Insertion Size Distribution),一个是TSS富集峰图

插入片段分布图

ATAC-seq的插入片段分布有着非常鲜明的特点,一般把<100 bp的片段区域称NFR(Nucleosome-Free Region)也就是无核小体区,这部分区域也是转座酶最容易切割的区域,每隔10.5 bp就有一个小齿,对应DNA螺旋一周的间距。200 bp有一个峰对应的是核小体单体的插入片段长度,再远点的400 bp和600 bp有两个小峰,对应核小体二聚体和核小体三聚体的插入片段长度。

TSS富集峰图

转录起始位点(Transcription Start Site,TSS)是没有核小体的,所以在ATAC-seq质控分析中,可以明显看到NFR在转录起始位点富集。以上图为例,我们选择TSS上下游3Kb的区域,NFR reads在TSS位点两侧有明显富集趋势。底下的热图也是同样的意思,每一行表示一个基因或者转录本,图中的红色区域也不一定要延伸到底,因为部分TSS可能没有在这个时期开放,这是很正常的现象。

这两个质控步骤可以先做第一个,第二个TSS富集峰图需要在peak calling和转换文件格式为.bw之后,使用Deeptools工具作图。

Reads Shifting

质控后还有一个步骤是进行reads shifting,前面说过Tn5酶切过程中会在上下游产生一个缺口,因此需要将正链正向移动4bp,负链负向移动5bp。

这一步在ACAT-seq的原文中有做,不做的话对单碱基分辨率要求比较高的分析是有影响的(比如转录因子足迹分析,下面的Motif Analysis会说)。

Peak Calling

peak calling是后续所有分析的基础。简单来说,在将reads比对到参考基因组后,因为进行的是pair-end测序,一对reads之间的序列为一个fragment,统计每个碱基上fragment的数量作图,哪个地方fragment数量多,在统计图上就会显示出一个峰,也就是一个peak。peak calling的过程就是检测染色质开放区的fragment富集信号。

这一步用的软件有比较经典的MACS2,这个软件可以处理ChIP-seq、ATAC-seq、CUT&TAG等等的数据,需要调整不同的参数。与ChIP-seq不同,ATAC-seq的Tn5转座酶酶切的是染色质开放区域,在TF结合区域的DNA是拉不下来的(反映在峰图上是一个谷,ChIP-seq是一个峰),因此在调整峰值偏移(peak shift)的时候,一般用shift-extend的方法进行分析,ATAC-seq需要向外shift。如下图:

比如我们测序reads长度是150bp,两条reads的5‘端代表Tn5的酶切位点,我们需要向外shift 75bp,让酶切位点处于reads的中间位置,再进行peak calling。

用上面的软件进行peak calling后会生成bedGraph文件,也就是.bdg后缀的文件,是bed文件的一种扩展,可以在IGV基因组浏览器中打开(可能会比较卡),也可以借助UCSC的工具bedGraphToBigWig转成BigWig文件后(.bw后缀)再到IGV基因组浏览器中打开。还有一个重要的结果文件是.narrowPeak后缀的文件,也可以直接导入IGV。

以上上游分析的步骤可以参考ATAC-seq data analysis: from FASTQ to peaks | Yiwei Niu’s Note

接下来是常见的一些下游分析的方法。

IDR Peak

对于一个样本如果做了多个重复,就需要对样本的可重复性进行评估。可以用ENCODE项目的一个软件包Irreproducible Discovery Rate (IDR) ,导入两个样本的.narrowPeak结果文件后作图分析,这个软件的作用是评估重复样本间peak的一致性,生成的图如下:

IDR算法同时考虑了peaks间的overlap和富集倍数的一致性。上面的图所有的点都是两个样本间相互overlap的peak,也就是都是可重复的,红点代表富集倍数是有差别的,黑点代表富集倍数是一致的,因此黑点数量越多越好。

Peak Annotation

拿到peak后,如果想要知道这些peak在基因组的哪些地方分布功能是什么,就需要对peak进行注释,常用的有R包ChIPseeker

Motif Analysis

这一部分能做的分析还是相当多的,列举几个:

  • 从头预测:预测新的motif,注释已存在的motif。软件:MEME+Tomtom
  • Motif扫描:除了开放染色质区域,寻找其他序列所有motif的位置信息。软件:FIMO
  • 转录因子富集:软件AME或者HOMER
  • 转录因子足迹(TF FootPrint):转录因子占位效应(转录因子结合在DNA上,阻止了Tn5酶切,在开放染色质区域留下一个缺失的位置),注意要进行前面说的reads shifting。软件R/centipade

常用的motif数据库:

Nucleosome positioning

核小体定位,从前面的插入片段分布图可以看出,在ATAT-seq文库中,核小体单体插入片段数量相比NFR明显少很多,但是也有一些软件比如NuleoATACHMMRATAC可以用于核小体的占位分析。

联合分析

  • ChIP-seq:由于转录因子的结合区域在染色质开放区,因此ATAC-seq的peak和ChIP-seq的peak之间存在部分重叠,因此这两个组学联用可以相互验证,而转录因子在ChIP-seq中独有的Peak则暗示这个转录因子可能是结合在异染色质区域的驱动型转录因子(Pioneer TFs)。对于组蛋白修饰的ChIP-seq而言,前面也说过组蛋白乙酰化与染色质开放区形成有重要联系,同样可以与ATAC-seq进行联合分析。
  • RNA-seq:比如将ATAC-seq的信号在gene body上的分布做比较,或者按照基因不同的表达量做分类,再与ATAC-seq联用分别统计不同表达量基因的TSS上的富集信号,研究ATAC-seq的富集信号是否与基因表达量相关、差异表达的基因是否受染色质可及性的调控等等。

2. CUT&Tag

CUT&Tag全称Cleavage Under Target & Tagmentation,翻译为靶向剪切及转座酶技术,是一种研究蛋白-DNA互作的技术,替代传统的ChIP-seq方法。开头介绍ATAC-seq和CUT&Tag非常相似,原因就在于CUT&Tag也用Tn5转座酶,只不过这个酶是Protein A/G融合的Tn5转座酶。当然,两者研究内容还是不一样的,下面详细说一下。

2.1 背景介绍

ChIP

前面介绍ATAC-seq的时候已经拿ChIP-seq做过对比,这里为了引出CUT&Tag还是对ChIP技术做个简单介绍。ChIP全称Chromatin Immunoprecipitation,翻译为染色质免疫共沉淀,顾名思义,这个技术有个鲜明的特征就是抗原抗体免疫反应。

下面是ChIP-seq的经典流程:

我们知道,抗原抗体反应具有专一性,所以我们制备特定的转录因子的抗体(一般是多克隆抗体),将细胞核内的染色质用甲醛交联固定后进行超声破碎,这个时候与转录因子结合的DNA序列不会被打断。加入抗体做免疫共沉淀,解交联后获得与转录因子结合的DNA序列,建库测序就可以做后面的分析。

ChIP技术分类

  • N-ChIP:用的是Native Chromatin,原生态染色质,DNA片段化的方法为酶切,用上面提到的MNase。只能处理结合能力比较强的蛋白(一般用于组蛋白修饰),蛋白复合体存在解离的风险,实验难度相对较大。
  • X-ChIP:用的是Cross-linked Chromatin,甲醛交联的染色质,DNA片段化方式为超声波。甲醛交联的背景一般比较高,抗体识别位点可能会被屏蔽。

ChIP-seq技术非常依赖于抗体质量,如果研究低表达的蛋白,制备抗体的也是很大的挑战。近年在鉴定转录因子结合位点上又出了个新技术DAP-Seq,通过体外蛋白表达技术,表达出带有Halo标签的转录因子蛋白。通过Halo标签的抗体富集对应的蛋白DNA复合物,从而使所有蛋白都可以被一种抗体(Halo标签抗体)富集,绕过了抗体制备的难题(和后面要说的没啥关系,写到技术分类这里提一嘴)。

CUT系列技术

  • CUT&RUN:Cleavage Under Targets and Release Using Nuclease,靶向剪切和核酸酶释放,这里用的核酸酶是与Protein A/G融合的MNase。
  • CUT&Tag:Cleavage Under Targets and Tagmentation,靶向剪切和转座酶,这里用的酶是Protein A/G融合的Tn5转座酶,是CUT&RUN技术的改进版。

两种技术用的酶不一样,Tn5转座酶可以插入测序标签,因此CUT&Tag比CUT&RUN节省了更多建库时间,1天就可以完成建库。

2.2 实验流程

整个实验流程可以分为6步:

  1. 收集细胞,刀豆蛋白的磁珠吸附细胞膜
  2. 细胞电穿孔后分别孵一抗和二抗,一抗结合目标蛋白,二抗放大信号
  3. Hyperactive pA/pG-Tn5 Transposon结合,这种改造的Tn5转座酶会特异性识别抗体并结合
  4. 加入镁离子或者钙离子激活Tn5转座酶,片段化DNA
  5. 提取DNA
  6. PCR扩增文库

上面的流程图可以看出,CUT&Tag与ATAC-seq的区别就在于,CUT&Tag用了抗体,Tn5转座酶进一步改造可以识别抗体。

因此CUT&Tag实验需要做阴性对照(IgG),CUT&Tag文库和ATCA文库差别就在于抗体识别的蛋白,如果抗体的特异性很差,比如在所有组蛋白上都有结合,那这个时候建库得到的就是ATCA文库而不是CUT&Tag文库。这个时候阴性对照就很重要了,如果在IgG上也能出正常的PCR结果,就说明抗体有问题或者实验设计有问题了。

因为CUT&Tag用的是Tn5转座酶,所以也有一个缺陷,比如要研究异染色质的蛋白与DNA互作就做不了,因为Tn5转座酶无法在异染色质区剪切DNA。或者你要研究的蛋白空间结构比较大,导致Tn5转座酶无法剪切到DNA,这个时候也不能用CUT&Tag。

2.3 生信分析

CUT&Tag生信分析和ATAC-seq几乎一模一样,下游分析也可以做IDR Peak、Peak Annotation和Motif Analysis,这里就不赘述了。

]]>
+ + + <p>前阵子去兰州开全国植物生物学大会,做实验的师弟问了我一个问题:什么是ATAC-seq?因为王茂军老师课题组有同学在做CUT&amp;Tag,我脑海里第一个出现的也是CUT&amp;Tag,两者的研究内容虽然不同,但是使用的分析方式是非常相似的。正好看到菲沙基因做过这两个技术的讲座,这篇博客就整理一下介绍这两个技术以及衍生技术的分析原理,加深下自己的理解,详细的分析流程留到以后需要做的时候再记录。</p> + + + + + + + + + + + + +
+ go-cqhttp登录异常(错误码45)的解决办法 @@ -470,29 +495,4 @@ - - 0基础学习基因组三代测序组装(7)——基因组组装质量评估(BUSCO、LAI指数) - - http://www.shelven.com/2023/03/01/a.html - 2023-03-01T12:39:05.000Z - 2023-03-03T06:30:24.000Z - - 通过前面的纠错和校正步骤,我们得到了组装完成的基因组序列,接下来就是进行基因组的组装质量评估。质量评估的软件和方法比较多,这里分两篇博客记录,本篇主要演示如何用BUSCO和LAI指数评价基因组组装质量。

复习一下前面说到的contig N50,按照contig从短到长的顺序依次相加,当相加的长度达到Contig总长度的一半,最后一个Contig长度即为contig N50.

contig N50是基因组组装质量的第一指标,一般来说越高越好,但是contig N50不能完全代表一个基因组组装质量的高低,比如reads的错误连接也会使contig N50变高。接下来介绍几个现在常用的评估基因组组装质量的软件和方法。

1. 保守型基因评估

BUSCO(Benchmarking Universal Single-Copy Orthologs)评估是在基因含量层面上评估基因组完整性。简单来说,通过已有的直系同源数据库进行基因组比对,同源的生物之间有保守基因序列,能比对上的基因数越多说明组装的结果越靠谱。

安装过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 1. 源码安装(需要安装前置软件)
git clone https://gitlab.com/ezlab/busco.git
cd busco/
python setup.py install

# 前置软件:
https://biopython.org/
https://pandas.pydata.org/
https://jgi.doe.gov/data-and-tools/software-tools/bbtools/
https://ftp.ncbi.nlm.nih.gov/blast/executables/blast+/LATEST
http://bioinf.uni-greifswald.de/augustus/
https://github.com/soedinglab/metaeuk
https://github.com/hyattpd/Prodigal
http://hmmer.org/
https://github.com/smirarab/sepp/
https://www.r-project.org/

# 2. conda安装(推荐)
conda install -c conda-forge -c bioconda busco=5.3.2

conda安装可能会比较慢,需要多试几次。实在不行就源码下载编译,不过需要下载非常多的前置软件,不同软件可能会有环境冲突问题、gcc版本问题等等(我花了大半天时间在折腾环境)。安装之后通过busco -h查看是否安装成功,如果提示缺什么软件就用conda补上(我当前环境中没有安装pandas就会有提示)。

通过busco --list-datasets可以查看当前有哪些物种的数据库,我的植物是双子叶龙胆目,这里的数据库只有真双子叶植物(eudicots)分支离的最近,因此选择这个数据库,v5版本所有单拷贝直系同源数据库网址https://busco-data.ezlab.org/v5/data/lineages/

下载的数据库放在busco_downloads文件夹中,解压即可使用:

1
2
nohup wget https://busco-data.ezlab.org/v5/data/lineages/eudicots_odb10.2020-09-10.tar.gz &
tar -zxvf eudicots_odb10.2020-09-10.tar.gz

busco的详细参数可以看官网的user guide User guide BUSCO v5.4.4 (ezlab.org)

简单讲一讲格式和能用到的参数:

1
2
3
4
5
6
7
8
9
10
11
12
busco -i [SEQUENCE_FILE] -l [LINEAGE] -o [OUTPUT_NAME] -m [MODE] [OTHER OPTIONS]
'''
主要参数:
-i序列文件位置
-l下载的同源物种保守基因数据库位置
-o输出文件名
-m模式,分为genome,proteins,transcriptome三种
其他参数:
--cpu设置cpu数量
--download在线下载数据库,根据分类有"all"、"prokaryota"、"eukaryota"和"virus" (不推荐,速度慢)
--offline离线模式,不会更新数据库
'''

以下是我跑的程序,大约用了1个小时:

1
busco -i /public/home/wlxie/NextPolish/01_rundir/genome.nextpolish.fasta -l /public/home/wlxie/busco_soft/busco/test_data/eukaryota/busco_downloads/lineages/eudicots_odb10 -o baima -m genome --cpu 8 --offline

截至2023/02/28,真双子叶植物库有2326个保守BUSCO基因序列,比对结果文件在short_summary.specific.xxx.xxx.txt中,如下:

  • Complete BUSCOs (C) 多少个基因完全比对上BUSCOs
  • Complete and single-copy BUSCOs (S) 多少个基因比对上单拷贝的BUSCOs
  • Complete and duplicated BUSCOs (D) 多少个基因比对上多拷贝的BUSCOs
  • Fragmented BUSCOs (F) 多少个基因部分比对上BUSCOs,可能基因只是部分存在
  • Missing BUSCOs (M) 多少个基因没有比对上BUSCOs,可能这些直系同源基因是缺失的

从上面的数据看,组装结果还是不错的。从中也可以看到BUSCO运行的两个步骤用metaeuk进行基因预测(真核生物可以用tBLASTn与对应的BUSCO数据库序列进行比对从而确定候选区域,然后使用 Augustus 软件进行基因结构预测,两个软件可以替代metaeuk,详细参数见官网),以及HMMER进行同源基因的比对,从而评估基因组组装的完整性。

官方还提供了相应的python程序绘制结果图(调用了R包ggplot2),先将BUSCO结果文件放到新建的文件夹,运行相应的py程序,指定工作目录即可:

1
2
3
4
5
mkdir summaries

cp baima/short_summary.specific.eudicots_odb10.baima.txt summaries

generate_plot.py -wd summaries

结果图如下:

当然,有结果数据就可以自己做更好看的图了,不一定要用官方的。

2. 长末端重复序列评估

2018年发表在Nucleic Acids Research上的一篇文章Assessing genome assembly quality using the LTR Assembly Index (LAI),研究者提出了一种对长末端重复序列(long terminal repeats,LTRs)评估从而评价基因组完整度的方法,并且开发了对应的分析工具LTR_retriever

具体的LTR注释我会在后续的基因组注释笔记中更新,这里暂时跳过原理和背景部分,介绍下文章中提出的评估核心——LAI指数(LTR Assembly Index,LAI),也就是长末端重复序列组装指数。raw LAI = (完整LTR-RTs长度/总LTR长度)*100,修正后,LAI = raw LAI + 2.8138 × (94 – 整个基因组LTR identity)

以下是一个完整的LTR-RTs的结构示意图:

文章还阐明LAI独立于基因组大小、LTR-RT含量以及基因空间评估指标(如BUSCO和CEGMA)等参数,可以用于鉴定低质量的基因组区域。使用这个指标要求**基因组中完整的LTR-RTs应至少占基因组0.1%且总LTR-RTs长度至少占5%**。

文章最后给出了LAI评价基因组完整度的三个指标:

分类LAI举例
Draft0 ≤ LAI < 10Apple (v1.0), Cacao (v1.0)
Reference10 ≤ LAI < 20Arabidopsis (TAIR10), Grape (12X)
Gold20 ≤ LAIRice (MSUv7), Maize (B73 v4)

2.1 LTR序列预测

LTR_retriever需要以LTR_finder和/或ltrharvest的LTR预测结果文件为输入,也可以整合两个软件的预测结果作为输入(或者其他符合格式的LTR结果文件),因此需要先安装并运行以上软件,我这里以文章中提到的软件和参数运行。

1
2
3
4
5
# LTR_finder、ltrharvest和LTR_retriever安装(ltrharvest是genometools软件的一部分)

conda install -c bioconda ltr_finder
conda install -c bioconda genometools-genometools
conda install -c bioconda ltr_retriever

LTR_finder预测LTR序列(参数均由作者给出,只有文件是自己的):

1
2
3
4
#!/bin/bash
#SBATCH -n 10

ltr_finder -D 15000 -d 1000 -L 7000 -l 100 -p 20 -C -M 0.85 /public/home/wlxie/NextPolish/01_rundir/genome.nextpolish.fasta > baima_ltrfinder.scn

参数解释:

-D NUM Max distance between 5’&3’LTR, default is 20000# 5’和3’LTR之间的最大距离

-d NUM Min distance between 5’&3’LTR, default is 1000

-L NUM Max length of 5’&3’LTR, default is 3500# 5’和3’LTR最大长度

-l NUM Min length of 5’&3’LTR, default is 100

-p NUM min length of exact match pair, default is 20# 完全匹配最小长度

-C detect Centriole, delete highly repeat regions# 检测中心粒,删除高度重复区域

-M NUM min LTR similarity threshold, default is 0.00, [0,1]#最小LTR相似度

ltrharvest预测LTR序列(ltrharvest参数均由作者给出,只有文件是自己的):

1
2
3
4
5
6
7
8
#!/bin/bash
#SBATCH -n 10

mkdir index

gt suffixerator -db /public/home/wlxie/NextPolish/01_rundir/genome.nextpolish.fasta -indexname index/baima -tis -suf -lcp -des -ssp -sds -dna

gt ltrharvest -index index/baima -minlenltr 100 -maxlenltr 7000 -mintsd 4 -maxtsd 6 -motif TGCA -motifmis 1 -similar 85 -vic 10 -seed 20 -seqids yes > baima_ltrharvest.scn

这里要注意下要先使用genome tools里的suffixerator创建基因组索引文件,然后才可以使用ltrharvest进行LTR预测。

创建基因组索引的参数不做解释了,可以 gt suffixerator -help 查看。稍微记录下ltrharvest参数:

-minlenltr specify minimum length for each LTR,default: 100

-mintsd specify minimum length for each TSD,default: 4

-motif specify 2 nucleotides startmotif + 2 nucleotides endmotif: ****

-motifmis specify maximum number of mismatches in motif [0,3],default: 4

-similar specify similaritythreshold in range [1..100%],default: 85.00

-vic specify the number of nucleotides (to the left and to the right) that will be searched for TSDs and/or motifs around 5’ and 3’boundary of predicted LTR retrotransposons, default: 60

-seed specify minimum seed length for exact repeats,default: 30

-seqids use sequence descriptions instead of sequence numbers in GFF3 output,default: no

以上两个软件以同样的LTR最小相似度0.85预测LTR,得到两个结果文件baima_ltrfinder.scnbaima_ltrharvest.scn

2.2 LAI指数计算

用上一步的输出的两个结果文件,运行LTR_retriever鉴定LTR和计算LAI指数。

1
2
3
4
#!/bin/bash
#SBATCH -n 20

LTR_retriever -genome /public/home/wlxie/NextPolish/01_rundir/genome.nextpolish.fasta -inharvest baima_ltrharvest.scn -infinder baima_ltrfinder.scn -threads 20

这一步运行结果如下:

其他文件可以后续做LTR分析用到,这里我们只要看最后一个LAI的计算结果文件:

可以看到这个结果文件中包含了整个genome和各个contig的raw LAI和LAI指数,这里就只看整个genome的LAI指数15.37,根据上面文章作者提到的分类,属于Reference级别,也就是说可以认为达到参考基因组级别。

]]>
- - - <p>通过前面的纠错和校正步骤,我们得到了组装完成的基因组序列,接下来就是进行基因组的组装质量评估。质量评估的软件和方法比较多,这里分两篇博客记录,本篇主要演示如何用BUSCO和LAI指数评价基因组组装质量。</p> - - - - - - - - - - - - -
- diff --git "a/categories/QQ\346\234\272\345\231\250\344\272\272/index.html" "b/categories/QQ\346\234\272\345\231\250\344\272\272/index.html" index 738e73cca3..11fc7293d2 100644 --- "a/categories/QQ\346\234\272\345\231\250\344\272\272/index.html" +++ "b/categories/QQ\346\234\272\345\231\250\344\272\272/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3877,7 +3882,7 @@

Phantom

- + @@ -4007,19 +4012,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4088,7 +4093,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4208,7 +4213,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4548,7 +4553,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/categories/index.html b/categories/index.html index a55281529e..94ef4fe8d0 100644 --- a/categories/index.html +++ b/categories/index.html @@ -22,7 +22,7 @@ - + @@ -1780,8 +1780,8 @@ } - - + + + @@ -3775,7 +3787,7 @@

Phantom

- + @@ -3905,19 +3917,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3986,7 +3998,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4106,7 +4118,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4446,7 +4458,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/categories/\344\270\211\347\273\264\345\237\272\345\233\240\347\273\204\345\255\246/index.html" "b/categories/\344\270\211\347\273\264\345\237\272\345\233\240\347\273\204\345\255\246/index.html" index 2d25a92ed8..c3bb25f37f 100644 --- "a/categories/\344\270\211\347\273\264\345\237\272\345\233\240\347\273\204\345\255\246/index.html" +++ "b/categories/\344\270\211\347\273\264\345\237\272\345\233\240\347\273\204\345\255\246/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/categories/\344\270\252\344\272\272\344\270\273\351\241\265/index.html" "b/categories/\344\270\252\344\272\272\344\270\273\351\241\265/index.html" index 807334a5cf..3b282e19e2 100644 --- "a/categories/\344\270\252\344\272\272\344\270\273\351\241\265/index.html" +++ "b/categories/\344\270\252\344\272\272\344\270\273\351\241\265/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3811,7 +3816,7 @@

Phantom

- + @@ -3941,19 +3946,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4022,7 +4027,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4142,7 +4147,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4482,7 +4487,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/categories/\345\215\225\347\273\206\350\203\236\350\275\254\345\275\225\347\273\204/index.html" "b/categories/\345\215\225\347\273\206\350\203\236\350\275\254\345\275\225\347\273\204/index.html" index 0a75219ae1..97503d70c5 100644 --- "a/categories/\345\215\225\347\273\206\350\203\236\350\275\254\345\275\225\347\273\204/index.html" +++ "b/categories/\345\215\225\347\273\206\350\203\236\350\275\254\345\275\225\347\273\204/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3752,7 +3757,7 @@

Phantom

- + @@ -3882,19 +3887,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3963,7 +3968,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4083,7 +4088,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4423,7 +4428,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/categories/\345\237\272\345\233\240\347\273\204\344\270\211\344\273\243\346\265\213\345\272\217\345\210\206\346\236\220/index.html" "b/categories/\345\237\272\345\233\240\347\273\204\344\270\211\344\273\243\346\265\213\345\272\217\345\210\206\346\236\220/index.html" index bff540c827..d9bc18bdb1 100644 --- "a/categories/\345\237\272\345\233\240\347\273\204\344\270\211\344\273\243\346\265\213\345\272\217\345\210\206\346\236\220/index.html" +++ "b/categories/\345\237\272\345\233\240\347\273\204\344\270\211\344\273\243\346\265\213\345\272\217\345\210\206\346\236\220/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4369,7 +4374,7 @@

Phantom

- + @@ -4499,19 +4504,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4580,7 +4585,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4700,7 +4705,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -5040,7 +5045,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/categories/\345\237\272\345\233\240\347\273\204\344\270\211\344\273\243\346\265\213\345\272\217\345\210\206\346\236\220/page/2/index.html" "b/categories/\345\237\272\345\233\240\347\273\204\344\270\211\344\273\243\346\265\213\345\272\217\345\210\206\346\236\220/page/2/index.html" index c7969fbf27..94e901a52d 100644 --- "a/categories/\345\237\272\345\233\240\347\273\204\344\270\211\344\273\243\346\265\213\345\272\217\345\210\206\346\236\220/page/2/index.html" +++ "b/categories/\345\237\272\345\233\240\347\273\204\344\270\211\344\273\243\346\265\213\345\272\217\345\210\206\346\236\220/page/2/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4150,7 +4155,7 @@

Phantom

- + @@ -4280,19 +4285,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4361,7 +4366,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4481,7 +4486,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4821,7 +4826,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/categories/\345\255\246\344\271\240\347\254\224\350\256\260/index.html" "b/categories/\345\255\246\344\271\240\347\254\224\350\256\260/index.html" index 977bf6457c..306a315ad2 100644 --- "a/categories/\345\255\246\344\271\240\347\254\224\350\256\260/index.html" +++ "b/categories/\345\255\246\344\271\240\347\254\224\350\256\260/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4370,7 +4375,7 @@

Phantom

- + @@ -4500,19 +4505,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4581,7 +4586,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4701,7 +4706,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -5041,7 +5046,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/categories/\345\255\246\344\271\240\347\254\224\350\256\260/page/2/index.html" "b/categories/\345\255\246\344\271\240\347\254\224\350\256\260/page/2/index.html" index c3417fadc5..f64417e6e4 100644 --- "a/categories/\345\255\246\344\271\240\347\254\224\350\256\260/page/2/index.html" +++ "b/categories/\345\255\246\344\271\240\347\254\224\350\256\260/page/2/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4347,7 +4352,7 @@

Phantom

- + @@ -4477,19 +4482,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4558,7 +4563,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4678,7 +4683,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -5018,7 +5023,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/categories/\345\255\246\344\271\240\347\254\224\350\256\260/page/3/index.html" "b/categories/\345\255\246\344\271\240\347\254\224\350\256\260/page/3/index.html" index 7ede48be1b..e94657eb81 100644 --- "a/categories/\345\255\246\344\271\240\347\254\224\350\256\260/page/3/index.html" +++ "b/categories/\345\255\246\344\271\240\347\254\224\350\256\260/page/3/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4347,7 +4352,7 @@

Phantom

- + @@ -4477,19 +4482,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4558,7 +4563,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4678,7 +4683,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -5018,7 +5023,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/categories/\345\255\246\344\271\240\347\254\224\350\256\260/page/4/index.html" "b/categories/\345\255\246\344\271\240\347\254\224\350\256\260/page/4/index.html" index c05ed78fdc..8f7ec3d36c 100644 --- "a/categories/\345\255\246\344\271\240\347\254\224\350\256\260/page/4/index.html" +++ "b/categories/\345\255\246\344\271\240\347\254\224\350\256\260/page/4/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4331,7 +4340,7 @@

Phantom

- + @@ -4461,19 +4470,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4542,7 +4551,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4662,7 +4671,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -5002,7 +5011,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/categories/\345\255\246\344\271\240\347\254\224\350\256\260/page/5/index.html" "b/categories/\345\255\246\344\271\240\347\254\224\350\256\260/page/5/index.html" index 9d6abb1641..a3d0e76759 100644 --- "a/categories/\345\255\246\344\271\240\347\254\224\350\256\260/page/5/index.html" +++ "b/categories/\345\255\246\344\271\240\347\254\224\350\256\260/page/5/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4150,7 +4216,7 @@

Phantom

- + @@ -4280,19 +4346,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4361,7 +4427,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4481,7 +4547,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4821,7 +4887,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/categories/\346\257\224\350\276\203\345\237\272\345\233\240\347\273\204\345\255\246/index.html" "b/categories/\346\257\224\350\276\203\345\237\272\345\233\240\347\273\204\345\255\246/index.html" index 3a121133b8..1a406f246b 100644 --- "a/categories/\346\257\224\350\276\203\345\237\272\345\233\240\347\273\204\345\255\246/index.html" +++ "b/categories/\346\257\224\350\276\203\345\237\272\345\233\240\347\273\204\345\255\246/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/categories/\346\267\261\345\272\246\345\255\246\344\271\240/index.html" "b/categories/\346\267\261\345\272\246\345\255\246\344\271\240/index.html" index d729f21a42..5e1b024242 100644 --- "a/categories/\346\267\261\345\272\246\345\255\246\344\271\240/index.html" +++ "b/categories/\346\267\261\345\272\246\345\255\246\344\271\240/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3873,7 +3878,7 @@

Phantom

- + @@ -4003,19 +4008,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4084,7 +4089,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4204,7 +4209,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4544,7 +4549,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/categories/\347\274\226\347\250\213\350\207\252\345\255\246/index.html" "b/categories/\347\274\226\347\250\213\350\207\252\345\255\246/index.html" index d786195185..daea46e6e4 100644 --- "a/categories/\347\274\226\347\250\213\350\207\252\345\255\246/index.html" +++ "b/categories/\347\274\226\347\250\213\350\207\252\345\255\246/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4361,7 +4366,7 @@

Phantom

- + @@ -4491,19 +4496,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4572,7 +4577,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4692,7 +4697,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -5032,7 +5037,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/categories/\347\274\226\347\250\213\350\207\252\345\255\246/page/2/index.html" "b/categories/\347\274\226\347\250\213\350\207\252\345\255\246/page/2/index.html" index d80408c461..e75fe7fb2e 100644 --- "a/categories/\347\274\226\347\250\213\350\207\252\345\255\246/page/2/index.html" +++ "b/categories/\347\274\226\347\250\213\350\207\252\345\255\246/page/2/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3817,7 +3822,7 @@

Phantom

- + @@ -3947,19 +3952,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4028,7 +4033,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4148,7 +4153,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4488,7 +4493,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/categories/\347\275\221\347\273\234\347\233\270\345\205\263/index.html" "b/categories/\347\275\221\347\273\234\347\233\270\345\205\263/index.html" index 3f956a88c0..3692a418c2 100644 --- "a/categories/\347\275\221\347\273\234\347\233\270\345\205\263/index.html" +++ "b/categories/\347\275\221\347\273\234\347\233\270\345\205\263/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4129,7 +4134,7 @@

Phantom

- + @@ -4259,19 +4264,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4340,7 +4345,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4460,7 +4465,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4800,7 +4805,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/categories/\347\276\244\344\275\223\345\237\272\345\233\240\347\273\204\345\255\246/index.html" "b/categories/\347\276\244\344\275\223\345\237\272\345\233\240\347\273\204\345\255\246/index.html" index b528631aef..2384a49ad2 100644 --- "a/categories/\347\276\244\344\275\223\345\237\272\345\233\240\347\273\204\345\255\246/index.html" +++ "b/categories/\347\276\244\344\275\223\345\237\272\345\233\240\347\273\204\345\255\246/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3818,7 +3823,7 @@

Phantom

- + @@ -3948,19 +3953,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4029,7 +4034,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4149,7 +4154,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4489,7 +4494,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/categories/\350\241\250\350\247\202\351\201\227\344\274\240\345\255\246/index.html" "b/categories/\350\241\250\350\247\202\351\201\227\344\274\240\345\255\246/index.html" new file mode 100644 index 0000000000..f509080365 --- /dev/null +++ "b/categories/\350\241\250\350\247\202\351\201\227\344\274\240\345\255\246/index.html" @@ -0,0 +1,4750 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 分类:表观遗传学 - 我的小破站 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + +
+ +
+
+ + +
+ + + +
+ + + + + + + + + + +
+
+ + + + + + + + + + +

+ +

+ +
+ +

前阵子去兰州开全国植物生物学大会,做实验的师弟问了我一个问题:什么是ATAC-seq?因为王茂军老师课题组有同学在做CUT&Tag,我脑海里第一个出现的也是CUT&Tag,两者的研究内容虽然不同,但是使用的分析方式是非常相似的。正好看到菲沙基因做过这两个技术的讲座,这篇博客就整理一下介绍这两个技术以及衍生技术的分析原理,加深下自己的理解,详细的分析流程留到以后需要做的时候再记录。

+ +
+ +
+
+ + + + + 学习笔记表观遗传学 + + + + + + +
+ + + +
+ + +
+ +
+ + + +
+ + + + + + + +
+ + + + + + + + + +
+ + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + diff --git "a/categories/\350\275\254\345\275\225\347\273\204\346\225\260\346\215\256\345\210\206\346\236\220/index.html" "b/categories/\350\275\254\345\275\225\347\273\204\346\225\260\346\215\256\345\210\206\346\236\220/index.html" index 1a0532e733..60ed425811 100644 --- "a/categories/\350\275\254\345\275\225\347\273\204\346\225\260\346\215\256\345\210\206\346\236\220/index.html" +++ "b/categories/\350\275\254\345\275\225\347\273\204\346\225\260\346\215\256\345\210\206\346\236\220/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4354,7 +4359,7 @@

Phantom

- + @@ -4484,19 +4489,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4565,7 +4570,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4685,7 +4690,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -5025,7 +5030,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/content.json b/content.json index 91b4220a1a..62a947988e 100644 --- a/content.json +++ b/content.json @@ -1 +1 @@ -{"meta":{"title":"我的小破站","subtitle":"","description":"","author":null,"url":"http://www.shelven.com","root":"/"},"pages":[{"title":"","date":"2022-04-09T14:14:36.602Z","updated":"2022-04-09T14:14:36.598Z","comments":true,"path":"404.html","permalink":"http://www.shelven.com/404.html","excerpt":"","text":"404 访问的页面走丢了!(⊙_⊙) 可能是输入地址有误或该地址已被删除 如果确认地址无误,请踢我一脚马上来改 TAT"},{"title":"","date":"2022-04-13T13:37:18.783Z","updated":"2022-04-13T13:37:18.770Z","comments":true,"path":"about/index.html","permalink":"http://www.shelven.com/about/index.html","excerpt":"","text":"感谢小伙伴的来访! 小破站尚在搭建中,有好的想法请留言~ 本站成立的初衷是记录本人学习笔记,以及整合各种有用的生信网站和工具,方便查阅和学习。本人是前端小白指bug越修越多,如果本站有bug请留言,非常感谢!各位dalao高抬贵手,请不要DDOS攻击_(:з」∠)_"},{"title":"所有分类","date":"2022-04-13T09:37:32.168Z","updated":"2022-04-13T09:37:32.164Z","comments":true,"path":"categories/index.html","permalink":"http://www.shelven.com/categories/index.html","excerpt":"","text":"没有你感兴趣的?0.0 请留言提出你的宝贵意见~"},{"title":"","date":"2023-02-23T14:46:44.404Z","updated":"2023-02-23T14:46:42.000Z","comments":false,"path":"history/index.html","permalink":"http://www.shelven.com/history/index.html","excerpt":"","text":"2022-2-23 更新日志:修复bug 修复文章分类页和热门标签页跳转错误bug 2022-1-14 更新日志:修复bug和栏目更新 修复评论区显示错误bug 更新生信网站导航栏目 2022-12-11 更新日志:修复bug和栏目优化 本地创建API,修复看板娘显示bug 迁移资源,修复本站响应时间过长bug 优化底部播放器,解决两个播放器播放不同步问题 2022-12-4 更新日志:栏目新增和优化 新增网址导航栏人工智能板块 优化文章显示方式,修复摘要显示bug 2022-11-29 更新日志:栏目优化 优化结绳栏目,笔记全部归档 2022-09-17 更新日志:评论系统更换 由于不可抗力因素原评论系统数据已无法恢复,今后将吸取教训做好备份 评论系统重新部署到Vercel,本站不再部署任何项目到腾讯云开发 2022-05-24 更新日志:图床迁移 本站图床已全部迁移至本地服务器,出于稳定性考虑不再使用jsdelivr 优化和新增网址导航栏内容 2022-05-18 更新日志:新增栏目 新增网址导航栏目,优化导航栏 2022-04-29 更新日志:图床迁移 github图床已挂,2022年4月29日之后本站图床将逐渐迁移本地 2022-04-19 更新日志:图床迁移 优化相册栏目 背景图片图床迁移至本地 2022-04-18 更新日志:新增栏目 新增相册栏目 2022-04-16 更新日志:看板娘优化 看板娘模块显示优化 2022-04-15 更新日志:页面优化 网站页脚优化 2022-04-14 更新日志:bug修复 修复文章永久链接中文乱码bug,已更改永久链接格式 修复部分页面强制重新加载bug,修改了部分pjax代码 修复aplayer播放器切换页面后中断播放bug,同上 2022-04-13 更新日志:bug修复和CDN加速 修复主页轮播图片空白bug,修改了部分parallax代码,已优化图片加载方式 本站已加入又拍云联盟,站点已进行CDN加速 小破站正式对外开放! 2022-04-12 更新日志:服务器迁移 服务器迁移至本地 本站已绑定www.shelven.com域名 本站已安装SSL安全证书 2022-04-11 更新日志:界面优化 添加pjax插件,优化部分页面加载速度 百度统计维护,5月31日恢复接入 bug:暂时无法全站无刷新加载 2022-04-10 更新日志:功能添加 添加rss订阅功能 新增结绳栏目 新增站内搜索功能 接入百度统计 2022-04-09 更新日志:功能添加 新增live2D看板娘模块 接入腾讯云开发和twikoo评论系统 启用MFA,新增留言提醒和QQ邮箱头像抓取功能 2022-04-08 更新日志:页面美化 更改鼠标样式 更改侧边栏配置 新增网页加载条 2022-04-07 更新日志:页面美化和功能更新 重设导航栏 重设封面 更改右键菜单功能 2022-04-06 更新日志:功能添加 添加背景音乐功能,接入aplayer播放器 添加网站统计访客数功能,接入LeanCloud 添加文章字数统计功能 2022-04-05 更新日志:页面美化 导航栏设计和美化 卡片添加透明度 优化主题背景图片 2022-04-04 更新日志:小破站成立啦! 使用Hexo框架,volantis主题搭建网站 github建库,代码托管 建立图床,使用jsDelivr进行CDN加速"},{"title":"","date":"2023-07-04T10:04:18.318Z","updated":"2023-07-04T10:04:16.000Z","comments":true,"path":"more/404.html","permalink":"http://www.shelven.com/more/404.html","excerpt":"","text":"404 访问的页面走丢了!(⊙_⊙) 可能是输入地址有误或该地址已被删除 如果确认地址无误,请踢我一脚马上来改 TAT"},{"title":"","date":"2022-04-13T07:44:09.805Z","updated":"2022-04-13T07:44:09.801Z","comments":true,"path":"mylist/index.html","permalink":"http://www.shelven.com/mylist/index.html","excerpt":"","text":""},{"title":"所有标签","date":"2022-04-13T09:37:59.531Z","updated":"2022-04-13T09:37:59.527Z","comments":true,"path":"tags/index.html","permalink":"http://www.shelven.com/tags/index.html","excerpt":"","text":"没有你感兴趣的?0.0 请留言提出你的宝贵意见~"},{"title":"壁纸分享(点击查看大图)","date":"2022-04-19T06:40:53.941Z","updated":"2022-04-19T06:40:53.937Z","comments":true,"path":"photo/index.html","permalink":"http://www.shelven.com/photo/index.html","excerpt":"","text":""},{"title":"实时微博热搜榜","date":"2022-04-27T18:04:09.706Z","updated":"2022-04-27T18:04:09.701Z","comments":true,"path":"weibo/index.html","permalink":"http://www.shelven.com/weibo/index.html","excerpt":"","text":""},{"title":"","date":"2022-12-01T15:33:37.918Z","updated":"2022-12-01T15:33:36.000Z","comments":true,"path":"Bioinformatics/index.html","permalink":"http://www.shelven.com/Bioinformatics/index.html","excerpt":"生信网站快速导航 本页面暂时无法使用站内搜索 绝对不是我懒得改代码 搜索请直接使用ctrl + F","text":"生信网站快速导航 本页面暂时无法使用站内搜索 绝对不是我懒得改代码 搜索请直接使用ctrl + F 交换友链请参照下列格式并留言 名字|name: Phantom链接地址|link: http://www.shelven.com/头像地址|avatar: https://www.shelven.com/tuchuang/avatar.jpg描述|desc: 博学而笃志,切问而近思"},{"title":"","date":"2022-12-03T15:45:17.107Z","updated":"2022-12-03T15:45:15.000Z","comments":false,"path":"AI/index.html","permalink":"http://www.shelven.com/AI/index.html","excerpt":"人工智能快速导航 和前面生信网站导航一样,暂时无法使用站内搜索 这一页收藏机器学习和人工智能相关网站","text":"人工智能快速导航 和前面生信网站导航一样,暂时无法使用站内搜索 这一页收藏机器学习和人工智能相关网站 人工智能常用文档PyTorch相关 Github仓库 PyTorch官网 PyTorch中文文档 OpenCV相关 Github仓库 OpenCV官网 OpenCV中文论坛 OpenCV官方文档 Numpy相关 Github仓库 Numpy官网 Numpy官方文档 Pandas相关 Github仓库 Pandas中文官网 Matplotlib相关 Github仓库 Matplotlib官网 Matplotlib中文文档 常用软件源 清华大学开源软件镜像站 阿里巴巴开源镜像站 豆瓣镜像源 北京外国语大学开源软件镜像站 算力平台 Colaboratory(推荐) Kaggle(推荐) 深度学习雾计算平台 矩池云 数据集各种类型的数据存储库 Kaggle Datasets Google Dataset Search Papers With Code DatasetList 格物钛 飞桨AI Studio 计算机视觉(CV)相关 ImageNet MNIST IMDB-WIKI VisualData 自然语言处理(NLP)相关 M-AILABS Speech Dataset LJ Speech Dataset LibriSpeech ASR corpus 标贝中文标准女声音库 人工智能竞赛网站 Kaggle 和鲸社区 DataFountain 华为云开发者大赛 阿里云天池 交换友链请参照下列格式并留言 名字|name: Phantom链接地址|link: http://www.shelven.com/头像地址|avatar: https://www.shelven.com/tuchuang/avatar.jpg描述|desc: 博学而笃志,切问而近思"}],"posts":[{"title":"go-cqhttp登录异常(错误码45)的解决办法","slug":"go-cqhttp登录异常(错误码45)的解决办法","date":"2023-08-04T14:57:57.000Z","updated":"2023-08-04T15:01:30.000Z","comments":true,"path":"2023/08/04/a.html","link":"","permalink":"http://www.shelven.com/2023/08/04/a.html","excerpt":"曾经写了一篇go-cqhttp扫码登录异常的解决方法点击此处,当时go-cqhttp版本为v1.0.0,如今(2023年8月4日)已不再适用。在v1.1.0版本之后需要自建或者使用别人搭建的签名服务器,否则会返回错误代码45,这里记录下自己的操作和踩的坑。","text":"曾经写了一篇go-cqhttp扫码登录异常的解决方法点击此处,当时go-cqhttp版本为v1.0.0,如今(2023年8月4日)已不再适用。在v1.1.0版本之后需要自建或者使用别人搭建的签名服务器,否则会返回错误代码45,这里记录下自己的操作和踩的坑。 问题描述go-cqhttp更新版本v1.1.0之后,无法通过替换device.json文件登录,返回错误码45,需要配置签名服务器。 解决方法1. linux操作系统解决方法1.1 安装和启动docker123456789# 下载docker安装脚本,一键安装curl -fsSL https://get.docker.com -o get-docker.shbash get-docker.sh# 启动docker服务systemctl start docker# 查看docker状态,是否正常启动service docker status 1.2 查看ANDROID_ID如果你的device.json文件还没有删除,打开它,找到android_id值,比如我这里是d9b3a88f3cd1f951(瞎打的,总之是这样子的格式)。 如果你已经删除上面的文件,再次运行go-cqhttp,会自动生成device.json文件,同上操作。 这个参数非常重要,每次重启go-cqhttp会随机生成新的android_id值,如果使用别人的签名服务器,需要将你的device.json文件中android_id值改成别人签名服务器提供的,两者的值要对应。 1.3 自建签名服务器实例化qsign的docker镜像: 12345678910# 开放防火墙端口(用docker默认的8080)firewall-cmd --permanent --add-port=8080/tcp# 重载防火墙firewall-cmd --reload# 运行dockerdocker run -d --restart=always --name qsign -p 8080:8080 -e ANDROID_ID=d9b3a88f3cd1f951 xzhouqd/qsign:8.9.63# 检查运行是否成功,访问主机端口没有报错就行curl localhost:8080 映射的主机端口不要改,就用默认的8080,否则docker运行后会无法访问映射的主机端口,curl l映射的端口会出现报错curl: (56) Recv failure: Connection reset by peer,运行go-cqhttp也会出现获取协议T544报错和获取sso sign报错reset by peer! 1.4 修改config.yml回到go-cqhttp目录,修改config.yml文件的sign-server字段,如下: 如果没有该字段,说明你的go-cqhttp版本不对,去github下载新的。 1.5 运行go-cqhttp如果你之前用过go-cqhttp,把主目录下的data/versions/6.json文件删除,否则会因为协议版本和签名服务器的协议版本不一致导致仍然报错45,仍然提示你配置sign service! 删除6.json后,同样会让你进行滑块验证,到上面提示的网址手动滑块验证后,可能和我一样会提示账号开启了设备锁,不要慌,同样是到提示的网站,用手机QQ扫一下(不需要服务器和手机QQ在同一个网络环境),然后再次运行go-cqhttp,再次进行滑块验证即可。 成功运行~ 2.windows操作系统解决方法2.1 查看ANDROID_ID这步和1.2一样,先从device.json获取android_id值,不再赘述。 2.2 自建签名服务器下载这个仓库Releases · fuqiuluo/unidbg-fetch-qsign (github.com),release版本V1.0.3 JAR,注意版本。 解压后可以看到两个目录bin和lib,在主目录下创建txlib/8.9.86这样结构的两个文件夹,将unidbg-fetch-qsign/txlib/8.9.63 at master · fuqiuluo/unidbg-fetch-qsign (github.com)这个仓库下的两个.so结尾的文件下载到8.9.86文件夹中。 回到主目录,创建一个批处理文件run.bat: 123.\\bin\\unidbg-fetch-qsign.bat --host=127.0.0.1 --port=8080 --count=1 --library=txlib\\8.9.63 --android_id=d890fd5ae11be94d# 这里--port可以自定义端口,--count表示最大连接数,--library就是两个.so文件的路径,--android_id是第一步你获取的值 双击run.bat,一会儿后看到[FEKit_]info: task_handle.h:74 TaskSystem not allow是正常的。 2.3 修改config.yml,运行go-cqhttp同1.4,1.5,不再赘述。 以上实现方式均参考自签名服务器相关问题 · Mrs4s/go-cqhttp · Discussion #2245 (github.com) 我自己在linux和windows上均试过没有问题,感谢提出解决方案的大佬。","categories":[{"name":"QQ机器人","slug":"QQ机器人","permalink":"http://www.shelven.com/categories/QQ%E6%9C%BA%E5%99%A8%E4%BA%BA/"}],"tags":[{"name":"qq bot","slug":"qq-bot","permalink":"http://www.shelven.com/tags/qq-bot/"},{"name":"go-cqhttp","slug":"go-cqhttp","permalink":"http://www.shelven.com/tags/go-cqhttp/"}]},{"title":"Hi-C染色体挂载(2)——3D-DNA配置运行和手动调整Hi-C互作图","slug":"Hi-C染色体挂载(2)——3D-DNA配置运行和手动调整Hi-C互作图","date":"2023-07-13T13:47:54.000Z","updated":"2023-07-17T13:19:41.000Z","comments":true,"path":"2023/07/13/a.html","link":"","permalink":"http://www.shelven.com/2023/07/13/a.html","excerpt":"前一篇博客主要讲了如何使用juicer进行Hi-C测序的下机数据处理,这篇博客我们按照Aiden团队的基因组组装“CookBook”继续接下来的操作,主要记录下3D-DNA软件的配置运行,以及如何手动调整结果。","text":"前一篇博客主要讲了如何使用juicer进行Hi-C测序的下机数据处理,这篇博客我们按照Aiden团队的基因组组装“CookBook”继续接下来的操作,主要记录下3D-DNA软件的配置运行,以及如何手动调整结果。 1. 下载和配置3D-DNA123456789101112131415# 拉取3D-DNA仓库git clone https://ghproxy.com/https://github.com/aidenlab/3d-dna.git## 将run-asm-pipeline-post-review.sh和run-asm-pipeline.sh属性分别改成可执行,对应chmod的0755权限chmod 755 run-asm-pipeline-post-review.shchmod 755 run-asm-pipeline.sh# 安装LastZ(跑二倍体模式需要用,一个DNA序列比对工具)conda install LastZ# 安装GNU Parallel## 官方推荐用该shell下实现并行运算的工具,加快程序运行速度。集群用户如果没有预装可以在~/bin中以如下方式编译安装wget http://ftp.gnu.org/gnu/parallel/parallel-latest.tar.bz2tar -xvjf parallel-latest.tar.bz2cd parallel-20230622./configure --prefix=$HOME && make && make install 3D-DNA有两个pipeline脚本,12个独立的功能模块(每个模块一个文件夹),官方给出的pipeline如下: 三个橘色框是需要手动调整的地方,稍微了解下这几个独立模块分别在做什么(具体参考manual_180319 (aidenlab.org)): 主要的8个独立模块(对应上面的流程): scaffold:对给定的scaffolds进行排序和定向,转换基于3D proximity的一系列单个片段序列成为单个的megascaffold,这个megascaffold用于检测错误的连接或者保留用于后续处理(split模块还会用到) visualize:与后续的Juicebox Assembly Tools处理对接,也就是可视化组装结果。在3D-DNA中用于连接各个处理阶段,便于审查和调整参数。 edit:包含检测3D-DNA错误连接的脚本和一些校正算法,这个模块在做特殊处理的时候最可能需要调整参数 polish:校正3D-DNA scaffolding算法可能引起的错误,类型基因组的polish过程 split:分割megascaffold成为染色体 seal:消除上一步结果的假阳性(通过引入碎片的方式,详细看手册说明) merge:融合代表替代单倍型的组装片段(仅在二倍体模式下)起作用 finalize:生成最终的fasta格式输出,在最终fasta生成过程中,考虑到可能的顺序和方向,将被识别为同一染色体内部的scaffold连接起来,同时在每个scaffold之间添加固定大小(500bp)的间隙 附加的4个独立模块: utils:一些其他模块用到的核心脚本,包括从.fasta文件生成.assembly文件 lift:一些从基因组草图到最后组装结果的核心脚本 supp:几个附加脚本,包括生成色谱图的脚本 data:一些数据表 如果要研究各个步骤的算法就可以在各个模块中找源码,现在的我只是个调包侠,程序不出问题就不深入研究了(以后有时间一定= =) 两个pipeline脚本也是对应不同阶段使用的: run-asm-pipeline.sh 将几个独立模块串联,组装基因组(也就是上面流程图用的脚本) run-asm-pipeline-post-review.sh 用于Juicebox Assembly Tools (JBAT)手动调整之后执行,只会执行上个脚本的后几个阶段(finalize或者seal和finalize),生成最终的hic文件(用于质控)和fasta序列 2. 运行3D-DNA了解不同脚本和模块作用后,接下来就是很简单的设置脚本参数运行了。 123456789101112131415161718# 创建run.slurm脚本vim run.slurm# 脚本内容如下---------------------------------------------------------------#!/bin/bash#SBATCH -n 30#SBATCH -N 1#SBATCH -t 7200/public/home/wlxie/biosoft/3d-dna/run-asm-pipeline.sh \\ /public/home/wlxie/biosoft/juicer/reference/genome.fa \\ -r 0 \\ /public/home/wlxie/biosoft/juicer/aligned/merged_nodups.txt---------------------------------------------------------------# 运行脚本sbatch run.slurm 220Mb参考基因组,50G的merged_nodups.txt文件,实际运行结束时长为10小时。 run-asm-pipeline.sh脚本用法和参数如下: 12345678910USAGE: ./run-asm-pipeline.sh [options] <path_to_input_fasta> <path_to_input_mnd>path_to_input_fasta:组装的基因组fasta文件位置path_to_input_mnd:juicer处理后得到的去重复的Hi-C reads比对基因组文件位置,也就是merged_nodups.txtOPTIONS:-m|--mode haploid/diploid 单倍体/二倍体模式(默认单倍体)-i|--input input_size 指定输入的 contig/scaffold size阈值,默认15000,小于15000的将被忽略-r|--rounds number_of_edit_rounds 指定纠错轮数(默认为2轮)-s|--stage stage 指定运行的阶段,可以是polish, split, seal, merge和finalize 对于运行模式,作者团队建议是先运行默认的haploid模式,检查拼接后的图谱中是否存在与杂合度不足相关的信号,diploid模式会尝试合并一些单倍型。 The diploid mode is to be used when one suspects large amounts of under collapsed heterozygosity to be present in the draft genome assembly (this was indeed the case for AaegL2). Running in the diploid mode will attempt to analyze the final assembly to remove the interspersed haplotigs and merge overlaps not recognized by the contigger because of variant differences. In most cases the default haploid mode should be used. 可以看官方的这篇文章帮助理解下两种模式的区别,以及软件LastZ在diploid模式中起的作用:Extended_Hi-C_methods.docx (dropbox.com) 至于纠错轮数,最好是按照默认的参数跑2轮,然后选择效果最好的一次结果导入juicerbox进行调整。 那么问题来了,怎么确认用哪个结果呢?首先要确认下结果文件有些啥。 3. 3D-DNA结果文件这个软件产生的结果文件非常多,中间步骤产生的文件是不会自动删除的,看一下主要的几个文件: .fasta 基因组序列文件,最终组装的基因组序列名称为.FINAL.fasta .hic 高度压缩的二进制文件,与下面的.assembly文件都可以载入Juicebox Assembly Tools,不同阶段产生不同的hic文件,0表示未校正 .assembly 空格分隔的文本,记录对基因组草图执行的指令,包括拆分、更改顺序、方向和锚定到染色体中(不同阶段都会产生,同时产生同名的.cprops和.asm,这两种文件已弃用) .scaffold_track.txt & .superscaf_track.txt scaffold和super scaffold的染色体边界文件,Juicebox 2D 注释格式,使用juicerbox的时候可以用到,但是使用Juicebox Assembly Tools不需要用(因为边界注释会根据. assembly 文件自动生成) .bed & .wig 对pipeline进行故障审查的时候用到,可以与.hic文件导入juicebox,暂时与Juicebox Assembly Tools不兼容 可以看出很多中间文件还是为了导入juicebox做分析产生的,而我们仅仅是为了辅助基因组组装,确认染色体的挂载情况,用到的是Aiden团队开发的juicebox的桌面应用拓展模块,也就是Juicebox Assembly Tools(JBAT),很多中间文件已经用不到了,这里我们需要用到的是.hic和.assembly两种类型文件,且这两种文件名称是一一对应的。 如果是默认参数跑的话还会有genome.1.hic和genome.2.hic两个文件,分别代表校正了一次和两次的结果,都可以导入JBAT看结果。 看很多教程其实陷入了误区,需要校正多少轮,用哪一组数据其实没有固定说法,有的人用genome.0.hic,有的人用genome.1.hic,都是可以的,这几个只是polish前和polish后产生的hic文件,别忘了我们还有其他阶段产生的hic文件,都是可以用的。这里我用genome.rawchrom.hic和对应的genome.rawchrom.assembly这组文件,因为这组文件实际上区分染色体的情况最好。 下一步就是导入JBAT手动调整染色体边界和修改组装过程中产生的错误。 4. 导入JBAT这一步在个人电脑上完成,最好保证电脑有4G以上的运存(2023年了个人电脑都有这个配置了吧),否则会巨卡无比。 我用的官方1.11.08版本的windows工具: https://s3.amazonaws.com/hicfiles.tc4ga.com/public/Juicebox/Juicebox_1.11.08.exe 不要安装直接可以运行,首先导入.hic文件: 导入hic文件之后Assembly菜单就可以点了,导入对应名字的.assembly文件: 完整导入后界面如图所示,主要是用到以下几个菜单栏: ①:标准化方式,选择balance看起来舒服点= = ②:分辨率,调整显示的分表率大小 ③:色彩范围,调整表示互作强度的色彩范围 ④右下角几个颜色块代表图上线条框的区域处于什么状态。默认黄色线框的是待编辑区(这里没有,对区块进行操作的时候才有),紫色线框是染色体区块,绿色线框是scaffold区域 5. 调整Hi-C互作图我这个互作图没有明显的需要染色体易位、倒位的调整,只需要确定染色体边界(也就是蓝色的线)就行。 染色体易位和倒位的调整在官方视频里就有,b站有人做了翻译,看一个视频足够了翻译 | Juicebox Assembly Tools教程_哔哩哔哩_bilibili 调高分辨率,比如下面我要做的就是将紫色框分成两个: 放大到一定分辨率后,scaffold的交界处鼠标会变成L型,点一下就可以加上染色体边界,这里截图没截出来…… 还有一种方法,长按shift键,单击拖动鼠标框选住你要操作的scaffold(不需要完全覆盖scaffold,只要框的面积里有你要的scaffold就行),松开shift键,这个时候选中的scaffold会处于待编辑状态(黄色的框线)。右键,选择Add chr boundaries即可为选中的scaffold区域添加染色体边界。 如果想要撤销操作,右键后点击Undo即可。 整体调整完以后是这样: 染色体条数不是无缘无故添加的,既要以图为准,又要由实验确定,或者通过已经发表的文献(该物种或者近缘物种)佐证,纠正算法产生染色体边界的误差。我是通过查阅文献得到这个物种染色体条数为11,调整后的Hi-C互作图也能明显分为11个互作区块,证实组装是没有问题的。 官方的操作手册上也有详尽的手动调整Hi-C互作图教程,可以参考manual_180322.pdf (dropbox.com)。 手动调整完后,导出调整后的.assembly文件,我们需要用这个文件重新回到3D-DNA生成最终的染色体水平基因组fasta文件。 6. 3D-DNA处理简单来说,我们在JBAT导出的最终.assembly文件记录了各个scaffold的坐标位置和互作信息,只需要在3D-DNA中跑最后finalize流程就可以了,软件提供了脚本run-asm-pipeline-post-review.sh: 12345678910111213USAGE: ./run-asm-pipeline-post-review.sh [options] -r <review.assembly> <path_to_input_fasta> <path_to_input_mnd> path_to_input_fasta:指定组装的草图基因组fasta文件path_to_input_mnd:指定juicer产生的merged_nodups.txtOPTIONS:-r|--review path_to_review_assembly 指定上一步JBAT导出的.assembly文件-i|--input input_size 指定输入的 contig/scaffold size阈值,默认15000,小于15000的将被忽略-s|--stage stage 指定运行开始的阶段,可以是seal或者finalize,默认finalize-q|--mapq mapq 指定最终map的可视化MAPQ阈值,默认1-g|--gap_size gap_size 指定最终scaffold之间的间隔,默认500--sort-output 对最终输出的染色体级别的scaffold长度排序,降序--build-gapped-map Option to output an additional contact map corresponding to the assembly after the gaps have been added between scaffolded sequences.(我也不知道干啥的) 比较重要的就三个文件位置参数: 123456789101112131415161718# 创建run_postview.slurm脚本vim run_postview.slurm# 脚本内容如下---------------------------------------------------------------#!/bin/bash#SBATCH -n 30#SBATCH -N 1#SBATCH -t 7200/public/home/wlxie/biosoft/3d-dna/run-asm-pipeline-post-review.sh \\ -r /public/home/wlxie/biosoft/3d-dna/baima_diploid/genome_rawchrom.review.assembly \\ /public/home/wlxie/biosoft/juicer/reference/genome.fa \\ /public/home/wlxie/biosoft/juicer/aligned/merged_nodups.txt---------------------------------------------------------------# 运行脚本sbatch run_postview.slurm 静静等待生成最终的fasta序列就可以啦,如果前面做过contig基因组草图注释的话,还需要将注释的基因信息转坐标到最终的染色体水平基因组上;如果没做过注释的话,跑一遍注释流程吧! 2023.7.17补充最终生成的基因组序列要看genome.FINAL.fasta,发现染色体数目和前面手动标定染色体边界的数目不一致,这很正常。可以用seqkit工具统计下各个染色体的长度,如下: 可以看到前11条是染色体级别的contig,这和手动标记的染色体数目一致,后面的contig长度占比非常小,可以标记区分一下,这些都要放在组装的基因组文件里。 顺便放一下自己写的python代码,同样的处理效果,顺手就当python练习了(运行前提:fasta文件中没有空行)。 123456789101112131415161718192021fasta_file = 'genome.FINAL.fasta' # 换成自己的fasta文件路径def fasta_length(file_path): sequences = {} with open(file_path, 'r') as file: content = file.read() # 读整个文件 blocks = content.split('>') # 根据关键字“>”分块 for block in blocks[1:]: # 第一个块为空,跳过 index = block.find('\\n') # 查找第一个换行符索引 newline_number = block.count('\\n') # 统计换行符数量 header = block[:index] # 序列名称 sequence_length = len(block) - index - newline_number # 序列长度 sequences[header] = sequence_length # 序列名称和长度存入字典 return sequencessequence_lengths = fasta_length(fasta_file)for header, length in sequence_lengths.items(): print(f'{header}:{length}')","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"Hi-C染色体挂载","slug":"Hi-C染色体挂载","permalink":"http://www.shelven.com/tags/Hi-C%E6%9F%93%E8%89%B2%E4%BD%93%E6%8C%82%E8%BD%BD/"},{"name":"3D-DNA","slug":"3D-DNA","permalink":"http://www.shelven.com/tags/3D-DNA/"},{"name":"JBAT","slug":"JBAT","permalink":"http://www.shelven.com/tags/JBAT/"}]},{"title":"Hi-C染色体挂载(1)——juicer2处理Hi-C数据","slug":"Hi-C染色体挂载(1)——juicer2处理Hi-C数据","date":"2023-07-11T10:04:56.000Z","updated":"2023-07-11T13:03:50.000Z","comments":true,"path":"2023/07/11/a.html","link":"","permalink":"http://www.shelven.com/2023/07/11/a.html","excerpt":"前面经过三代数据结合二代数据的组装和polish,已经把基因组组装成了contigs的水平,下一步就是进一步提升到染色体水平。从实现的方式上来说有Bionano的光学图谱技术(作用是减少Scaffold数量,基因组纠错),Hi-C技术,遗传图谱以及依靠算法实现的基于近缘物种参考基因组的染色体水平组装(比如RagTag)。","text":"前面经过三代数据结合二代数据的组装和polish,已经把基因组组装成了contigs的水平,下一步就是进一步提升到染色体水平。从实现的方式上来说有Bionano的光学图谱技术(作用是减少Scaffold数量,基因组纠错),Hi-C技术,遗传图谱以及依靠算法实现的基于近缘物种参考基因组的染色体水平组装(比如RagTag)。 对于一个没有遗传图谱,也没有近缘物种参考基因组的物种,考虑到光学图谱费用昂贵,一般最划算用的最多的是测一个Hi-C来进行基因组染色体级别组装。一些经典的Hi-C辅助组装的软件应运而生,比如LACHESIS、3D-DNA、YaHS等等。这篇笔记主要记录下软件3D-DNA的染色体挂载流程。 然而3D-DNA不能直接处理Hi-C下机数据,因此总的流程是用juicer2先处理Hi-C数据,再用3D-DNA进行染色体挂载,最后用Juicebox Assembly Tools (JBAT)进行手工纠错。流程参考自Baylor College of Medicine & Rice University Aiden团队Genome Assembly Cookbook,这几个软件也是出自他们团队。 juicer2Juicer是一款非常经典的Hi-C数据处理软件,但是配置运行起来稍微有点麻烦,花了小半天时间踩坑记录一下。需要非常注意的一点,直接从github官网拉取的juicer仓库是juicer2,在release版本中有一个稳定版juicer1.6,两者的配置和产生的结果文件是不一样的!!! 秉着软件用新不用旧的原则,本篇笔记全程用的是juicer2,如果有软件运行问题可以在3D Genomics - Google 网上论坛交流,或者在github官方提Issues(不建议)。 顺便提一下我使用Juicer2过程中碰到的版本问题: juicer2配置的juicer_tools版本要在2.0以上,否则会报错Exception in thread "main" java.lang.RuntimeException: Unknown command: statistics,该报错会直接导致无法生成inter.txt、inter_hists.m 、inter_30.txt和inter_30_hists.m文件,也就是Hi-C互作的统计数据和矩阵。 juicer2结果文件中不会自动产生merged_nodups.txt文件,该文件原本作为3D-DNA的输入文件,在juicer2中被同名的merged_nodups.bam文件和merged*.txt代替。如果想要merged_nodups.txt文件,需要加上参数--assembly。 1. 下载和配置juice212345678910111213# 拉取最新版本的juicer2仓库(这里用的github镜像,集群可能有dns污染无法直接访问github)git clone https://ghproxy.com/https://github.com/aidenlab/juicer.gitcd juicer# juicer主目录下配置scripts(必需)## 个人电脑的是建立软链接到CPU文件夹下,其他如slurm、LSF、AWS和Univa等集群或者云端跑juicer是建立软链接或者复制到对应文件夹的scripts文件夹下ln -s CPU scripts# scripts/common文件夹中下载juicer_tools(必需)cd scripts/commonwget -c https://ghproxy.com/https://github.com/aidenlab/Juicebox/releases/download/v2.20.00/juicer_tools.2.20.00.jar# 修改文件属性为可执行文件后,建立软链接ln -s juicer_tools.2.20.00.jar juicer_tools.jar 本来想配置slurm跑的,因为塔大集群使用的是slurm作业调度系统。花了好大功夫配置环境发现运行SLURM/scripts底下的juicer.sh还需要配置CUDA,晕,学校的集群没有GPU(怨念 = =)……所以如果集群没有GPU的话就老老实实用CPU文件夹下的juicer.sh,按照CPU流程跑后面的程序,缺点是不能用集群提交多线程作业非常难受。 官方也是推荐小的Hi-C实验可以用CPU版本,如果数据量比较大,还是推荐带有GPU加速的集群(最好是SLURM)或者云端跑。如下是官方推荐的两种方式: Running Juicer on a cluster · aidenlab/juicer Wiki (github.com) ENCODE-DCC/hic-pipeline: HiC uniform processing pipeline (github.com) juicer主目录下需要配置两个必需的文件夹reference和restriction_sites(第二个在juicer流程中非必须,但是做染色体挂载一定要) 12345678910# juicer目录下创建参考基因组文件夹,放入参考基因组(复制或者软链接)和构建参考基因组索引文件(必需用bwa)mkdir reference && cd referencecp /path/to/your/reference/genome.fa ./bwa index genome.fa# juicer目录下创建限制性酶切位点文件夹,MboI酶酶切参考基因组(根据自己测hic用的限制酶)mkdir restriction_sites && cd restriction_sitespython ~/biosoft/juicer/misc/generate_site_positions.py MboI genome ~/biosoft/juicer/reference/genome.fa## 生成的酶切图谱文件名为genome_MboI.txt,同个文件夹下生成contig长度文件awk 'BEGIN{OFS="\\t"}{print $1, $NF}' genome_MboI.txt > genome.chrom.sizes 官方的酶切参考基因组的脚本generate_site_positions.py支持HindIII、DpnII、MboI、Sau3AI和Arima4种限制性核酸内切酶,如果你Hi-C测序用的是其他酶,可以根据实际修改这个脚本中的字典类型数据patterns,根据实际情况加入键值对信息: 稍微解释一下官网给的awk 'BEGIN{OFS="\\t"}{print $1, $NF}'这个命令,这里awk的程序部分由两部分组成: BEGIN{OFS="\\t"}:BEGIN块是在处理文件之前执行的代码块。在这里,它设置输出字段分隔符(OFS)为制表符(\\t)。这意味着输出的字段将使用制表符进行分隔。 {print $1, $NF}:这是awk的主要部分,它定义了要执行的操作。在这里,它打印每行的第一个字段$1和最后一个字段(NF)。通过使用逗号分隔它们,它们将以制表符分隔的形式打印出来。 genome.chrom.sizes文件如下所示: 工作目录下必需配置一个文件夹fastq,其中存放Hi-C的双端测序数据,命名格式参考juicer.sh文件中的正则表达式*_R*.fastq*。 1234# 工作目录下创建存放hic测序数据的文件夹,我这里为了方便查看,工作目录就是juicer主目录,测序数据可以用软链接的形式存放mkdir fastq && cd fastqln -s ~/hi-c/BM_R1.fq.gz Av_R1.fastq.gzln -s ~/hi-c/BM_R2.fq.gz Av_R2.fastq.gz 2. 运行juicer2官网的juicer.sh参数非常之多,我这里只展示一些关键的,其他用默认参数即可。1.x版本可以参考Usage · aidenlab/juicer Wiki (github.com),2.0版本具体可以用bash juicer.sh -h查看,两个版本指令稍有不同。CPU版本和其他集群版本也稍有不同,以CPU中的2.0版本为例: 12345678910111213141516171819202122232425262728Usage: juicer.sh [-g genomeID] [-d topDir] [-s site] [-a about] [-S stage] [-p chrom.sizes path] [-y restriction site file] [-z reference genome file] [-D Juicer scripts parent dir] [-b ligation] [-t threads] [-T threadsHic] [-i sample] [-k library] [-w wobble] [-e] [-h] [-f] [-j] [-u] [-m] [--assembly] [--cleanup] [--qc]-g genomeID 必需项,指定基因组和版本,比如人类的"hg19" 或者小鼠的"mm10",如果没有,可以用-z命令替代,指定参考基因组文件-z reference genome file 指定参考基因组文件,此文件中必需有BWA索引文件-d topDir 指定输出目录,默认是当前目录。[topDir]/fastq必需包含fastq文件-s site 必需项,指定限制性核酸内切酶。如果不做片段级别的分析可以写none-S stage 指定运行pipeline的阶段,固定是"chimeric", "merge", "dedup", "final", "postproc", "early"其中之一,报错调试用 chimeric: alignment结束或者从aligned文件开始 merge: alignment结束但是没有生成merged_sort文件 dedup: 文件已经merge结束,但是没有生成merged_nodups文件 final: reads在merged_nodups文件中已经删除重复,但是尚未生成最终的state和hic文件 postproc: hic文件已经生成,只有特征注释尚未完成 early: 在hic文件生成前提前结束程序-p chrom.sizes path 指定染色体长度文件-y restriction site file 指定参考基因组酶切文件-D Juicer scripts parent dir 指定Juicer/scripts所在目录-t threads 指定跑BWA的线程数-T threadsHic 指定生成hic文件用的核数(2.0版本新增)--assembly 接下游3D-DNA分析,会提前结束并生成老版本的merged_nodups文件(2.0新增)--cleanup 如果pipeline成功运行,自动清除中间文件(2.0新增)如果是在集群中跑juicer,以下两个参数也是必需要改的,否则用默认分区名会直接报错:-q queue 指定跑alignments的队列分区(默认commons),slurm中的分区也就是partition,可以理解为LSF、PBS等作业调度系统中的队列-l long queue 指定跑需要时间更长的job,比如生成hic文件的队列分区(默认long) 因为我不是做三维基因组,Hi-C只测了一个样(主要用于辅助基因组组装,染色体挂载),比如做三维基因组就会有大量的Hi-C数据,juicer也支持多个结果的合并(使用mega.sh),详细也可以参考上面的juicer Wiki。 上面的限速步骤主要是跑BWA,因为第一次跑juicer不知道要多久,就稍微申请多一点的核数(虽然集群没有GPU,但是CPU资源很充足平常没人用)。 12345# 创建一个slurm作业vim juicer.slurm# 提交slurm作业sbatch juicer.slurm juicer.slurm文件内容如下(再次提醒--assembly 参数非常重要): 12345678910111213#!/bin/bash#SBATCH -N 1#SBATCH -n 30#SBATCH -t 7200/public/home/wlxie/biosoft/juicer/scripts/juicer.sh \\-z /public/home/wlxie/biosoft/juicer/reference/genome.fa \\-p /public/home/wlxie/biosoft/juicer/restriction_sites/genome.chrom.sizes \\-y /public/home/wlxie/biosoft/juicer/restriction_sites/genome_MboI.txt \\-s MboI \\-D /public/home/wlxie/biosoft/juicer \\-t 30 \\--assembly 运行时间可以参考一下我这里220 Mb的参考基因组,Hi-C测序数据39G(双端测序,gz文件),实际上运行了13小时才出结果。 运行pipeline成功后会在日志中提示(-: Pipeline successfully completed (-: 3. 结果文件CPU模式下会在工作目录创建aligned和splits文件夹: splits 文件夹存放整个pipeline的临时文件,跑完流程并且确认结果没问题后可以运行cleanup.sh或者直接删除(按照帮助文档的说法,–cleanup参数应该可以自动删除,我没有试过) aligned 文件夹存放运行结果 如果你是用集群模式跑的,比如SLURM,还会生成一个debug文件夹(CPU模式下用SLURM跑的没有该文件夹),可以查看各个步骤的error报告和output报告 aligned文件夹有以下结果文件: 我这里实际上是多了一些文件(实际上跑了两遍),因为一开始没有加上--assembly参数,所以一直运行到出hic结果文件,而且没有merged_nodups.txt。 我做染色体挂载只需要最后一个文件,第二次运行加了上面的参数,并且加上-S final参数,这样就省去了前面比对和去重复的时间(90%以上的时间花在这里),一个小时可以重新跑完出结果。 顺便了解一下其他结果文件的用途: inter.hic / inter_30.hic: The .hic files for Hi-C contacts at MAPQ > 0 and at MAPQ >= 30, respectively merged_nodups.txt: The Hi-C contacts with duplicates removed. This file is also input to the assembly and diploid pipelines (后面跑3D-DNA的输入文件) inter.txt, inter_hists.m / inter_30.txt, inter_30_hists.m: The statistics and graphs files for Hi-C contacts at MAPQ > 0 and at MAPQ >= 30, respectively. These are also stored within the respective .hic files in the header. The .m files can be loaded into Matlab. The statistics and graphs are displayed under Dataset Metrics when loaded into Juicebox (可以后续导入juicer box)","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"juicer2","slug":"juicer2","permalink":"http://www.shelven.com/tags/juicer2/"},{"name":"Hi-C染色体挂载","slug":"Hi-C染色体挂载","permalink":"http://www.shelven.com/tags/Hi-C%E6%9F%93%E8%89%B2%E4%BD%93%E6%8C%82%E8%BD%BD/"}]},{"title":"python自学笔记(8)——10种排序方式的python实现","slug":"python自学笔记(8)——10种排序方式的python实现","date":"2023-07-04T15:14:44.000Z","updated":"2023-07-04T15:37:54.000Z","comments":true,"path":"2023/07/04/a.html","link":"","permalink":"http://www.shelven.com/2023/07/04/a.html","excerpt":"最近中期答辩结束,稍微有点空闲的时间捋一捋数据结构和算法方面的知识。虽然现在用python实现排序就一个sort()函数的事,但是还是想锻炼下自己的思维,从底层代码学习一下10种经典排序的实现方式。","text":"最近中期答辩结束,稍微有点空闲的时间捋一捋数据结构和算法方面的知识。虽然现在用python实现排序就一个sort()函数的事,但是还是想锻炼下自己的思维,从底层代码学习一下10种经典排序的实现方式。 对于时间复杂度和空间复杂度的计算,自己还是一知半解,每种排序方式后面放了自己的理解,有错误会继续修改,最后有一张菜鸟教程总结的图可以参考。 本篇笔记的内容主要是跟着b站up主做的,代码整理来自英雄哪里出来的个人空间 我这里先定义一个生成随机整数序列的函数,后面都会调用,就不重复写了: 12345678import random# 生成指定范围、长度的序列def generate_random_sequence(len, min, max): seq = [] for i in range(len): seq.append(random.randint(min, max)) return seq 1. 选择排序从第一个元素到最后一个元素中选择最小的元素,和第一个元素进行交换;然后从第二个元素到最后一个元素选择最小的元素,和第二个元素交换,依此类推。通过对未排序的元素比较和交换,选择出最小的,直到最后成为一个升序序列。 1234567891011121314151617181920212223242526def SelectionSort(a): n = len(a) for i in range(n - 1): # 排完倒数第二个数之后就不用再排了,所以这里用n-1而不是n min = i for j in range(i + 1, n): if a[j] < a[min]: min = j a[i], a[min] = a[min], a[i] print(a)a = generate_random_sequence(10, 1, 100)print(a)SelectionSort(a)'''输出结果:[77, 76, 21, 51, 29, 23, 19, 68, 53, 37][19, 76, 21, 51, 29, 23, 77, 68, 53, 37][19, 21, 76, 51, 29, 23, 77, 68, 53, 37][19, 21, 23, 51, 29, 76, 77, 68, 53, 37][19, 21, 23, 29, 51, 76, 77, 68, 53, 37][19, 21, 23, 29, 37, 76, 77, 68, 53, 51][19, 21, 23, 29, 37, 51, 77, 68, 53, 76][19, 21, 23, 29, 37, 51, 53, 68, 77, 76][19, 21, 23, 29, 37, 51, 53, 68, 77, 76][19, 21, 23, 29, 37, 51, 53, 68, 76, 77]''' 这个算法时间复杂度(算法中循环执行的次数,量级估算)为O(n^2),其中n是输入序列的长度。空间复杂度(算法运行过程中临时占用存储大小,量级估算)为O(1),因为它只需要使用常数级别的额外空间。 2. 冒泡排序通过不断比较相邻元素,将数值大的元素往后排。第一个元素和第二个元素比较,如果第一个元素大,则进行交换,再比较第二个元素和第三个元素大小,以此类推,直到最大的元素移动到最后一个位置,然后进行第二轮比较。每一轮中数值较大的元素,不断到达数组的尾部。 123456789101112131415161718192021222324def BubbleSort(a): n = len(a) for i in range(n - 1, 0, -1): # range()左闭右开,n-1可以取到右边界,逆序枚举 for j in range(0, i): if a[j] > a[j + 1]: a[j], a[j + 1] = a[j + 1], a[j] print(a)a = generate_random_sequence(10, 1, 100)print(a)BubbleSort(a)'''输出结果:[57, 86, 40, 11, 38, 46, 21, 38, 18, 6][57, 40, 11, 38, 46, 21, 38, 18, 6, 86][40, 11, 38, 46, 21, 38, 18, 6, 57, 86][11, 38, 40, 21, 38, 18, 6, 46, 57, 86][11, 38, 21, 38, 18, 6, 40, 46, 57, 86][11, 21, 38, 18, 6, 38, 40, 46, 57, 86][11, 21, 18, 6, 38, 38, 40, 46, 57, 86][11, 18, 6, 21, 38, 38, 40, 46, 57, 86][11, 6, 18, 21, 38, 38, 40, 46, 57, 86][6, 11, 18, 21, 38, 38, 40, 46, 57, 86]''' 这个算法的时间复杂度为O(n^2)(最好的情况下数据本来有序,复杂度O(n)),其中n是待排序数组的长度。空间复杂度为O(1),因为只使用了常数级别的额外空间。和选择排序算法是一样。 3. 插入排序对前i-1个数已经有序的情况下,将第i个数插入到合适的位置。 将第二个元素和第一个元素比较,如果第二个元素小于等于第一个元素,则将第一个元素向后移动,并将第一个元素执行插入,这样前两个元素就是有序的。接着进行第二轮比较,也就是将第三个元素依次和第二元素和第一个元素比较,并插入到合适的位置,使前三个元素有序。以此类推,迭代执行n-1次插入,每次插入都是将元素插入到有序序列中。 123456789101112131415161718192021222324252627def InsertionSort(a): n =len(a) for i in range(1, n): x = a[i] j = i - 1 while j >= 0 and x <= a[j]: # 若x<=a[j],则将a[j]往后移动,继续判断前一个数 a[j + 1] = a[j] j -= 1 a[j + 1] = x print(a)a = generate_random_sequence(10, 1, 100)print(a)InsertionSort(a)'''输出结果:[95, 69, 37, 81, 21, 11, 53, 12, 22, 16][69, 95, 37, 81, 21, 11, 53, 12, 22, 16][37, 69, 95, 81, 21, 11, 53, 12, 22, 16][37, 69, 81, 95, 21, 11, 53, 12, 22, 16][21, 37, 69, 81, 95, 11, 53, 12, 22, 16][11, 21, 37, 69, 81, 95, 53, 12, 22, 16][11, 21, 37, 53, 69, 81, 95, 12, 22, 16][11, 12, 21, 37, 53, 69, 81, 95, 22, 16][11, 12, 21, 22, 37, 53, 69, 81, 95, 16][11, 12, 16, 21, 22, 37, 53, 69, 81, 95]''' 这个算法的时间复杂度为O(n^2),其中n是待排序数组的长度。空间复杂度为O(1),因为只使用了常数级别的额外空间。 要改善选择排序的时间复杂度,可以考虑使用其他更高效的排序算法,例如快速排序(Quick Sort)或归并排序(Merge Sort)。这些算法的时间复杂度通常为O(nlogn),比上面三个排序算法的O(n^2)更快。此外,还可以考虑使用内置的排序函数,如Python中的sorted()函数,它使用了优化的排序算法。如果待排序数组已经基本有序,可以通过引入一些优化措施来提高插入排序的性能,例如使用二分查找来确定插入位置,减少比较和交换的次数。 4. 归并排序利用分治的思想,采用递归的形式,对元素进行排序。当有两个长度为n的有序数组时,可以通过两个指针的移动,在O(n)的时间复杂度内,快速合并成一个有序数组。而这两个长度为n的有序数组,也可以通过两个n/2的有序数组合并而来。因此,只要不断对数组进行分治,就可以把一个无序数组变成有序。 对数组拆分的过程用到递归,对数组组合的过程用到了合并,故命名归并排序。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950# 实现一个合并函数(a列表start到mid的元素,以及mid+1到end的元素,需要分别按照递增顺序排列),执行后a列表start到end的元素也按照递增顺序排序def Merge(a, start, mid, end): tmp = [] l = start # l和r是两个区间的起点 r = mid + 1 # 当两个区间都未到达右端点,判断a[l]和a[r],小的值放入临时列表,下标自增 while l <= mid and r <= end: if a[l] <= a[r]: tmp.append(a[l]) l += 1 else: tmp.append(a[r]) r += 1 # 跳出循环时,剩余部分全部放入临时列表(因为跳出循环表示一个列表已经排完了,另一个列表剩下的也是有序的) tmp.extend(a[l : mid + 1]) tmp.extend(a[r : end + 1]) # 临时列表值拷贝回原列表a,完成一次合并 for i in range(start, end + 1): a[i] = tmp[i - start] print(start, end, tmp)# 实现一个递归函数(作用是拆分子数组)def MergeSort(a, start, end): # 待排序元素只有一个则返回 if start == end: return # 计算中点mid,整数除法,向下取整 mid = (start + end) // 2 MergeSort(a, start, mid) MergeSort(a, mid + 1, end) # 两次调用MergeSort()产生两个有序数组,之后对两个有序数组进行Merge()合并 Merge(a, start, mid, end)a = generate_random_sequence(10, 1, 100)print(a)MergeSort(a, 0, 9)'''输出结果:[92, 87, 91, 24, 5, 36, 76, 62, 66, 89]0 1 [87, 92]0 2 [87, 91, 92]3 4 [5, 24]0 4 [5, 24, 87, 91, 92]5 6 [36, 76]5 7 [36, 62, 76]8 9 [66, 89]5 9 [36, 62, 66, 76, 89]0 9 [5, 24, 36, 62, 66, 76, 87, 89, 91, 92]''' 其实也可以不用递归的方法,用迭代的方式来实现归并排序: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657# 实现一个合并函数def merge(arr, start, mid, end, temp): i = start j = mid k = start while i < mid and j < end: if arr[i] < arr[j]: temp[k] = arr[i] i += 1 else: temp[k] = arr[j] j += 1 k += 1 while i < mid: temp[k] = arr[i] i += 1 k += 1 while j < end: temp[k] = arr[j] j += 1 k += 1 # 实现一个拆分子数组和合并的函数def merge_sort(arr): if len(arr) <= 1: return arr # 创建一个临时数组用于存储排序结果 temp = [0] * len(arr) # 设置初始步长为1 step = 1 while step < len(arr): # 按照步长将数组分为多个子数组进行合并 for start in range(0, len(arr), 2 * step): # 计算子数组的起始索引、中间索引和结束索引 mid = min(start + step, len(arr)) end = min(start + 2 * step, len(arr)) # 合并两个子数组 merge(arr, start, mid, end, temp) # 将临时数组的结果复制回原始数组 arr[:] = temp[:] # 增加步长 step *= 2 print(arr) return arrarr = generate_random_sequence(10, 1, 100)print(arr)sorted_arr = merge_sort(arr)'''输出结果:[65, 50, 64, 33, 58, 7, 97, 87, 92, 52] (原序列)[50, 65, 33, 64, 7, 58, 87, 97, 52, 92] (步长2)[33, 50, 64, 65, 7, 58, 87, 97, 52, 92] (步长4)[7, 33, 50, 58, 64, 65, 87, 97, 52, 92] (步长8)[7, 33, 50, 52, 58, 64, 65, 87, 92, 97] (最终序列)''' 在这个实现中,使用了一个临时数组temp来存储排序的结果。首先,设置初始步长为1,然后在每一轮迭代中,按照步长将原始数组分为多个子数组,并调用merge函数将这些子数组进行合并。合并后的结果存储在临时数组temp中。最后,将临时数组的结果复制回原始数组。迭代的思想在于通过不断迭代地合并子数组,直到得到完整的有序数组。 归并排序的时间复杂度是O(nlogn),其中n是待排序数组的大小。空间复杂度是O(n),因为在每次合并操作中需要创建一个临时列表来存储合并后的结果。 时间复杂度的算法可以参考如何计算归并排序算法的时间复杂度? 空间复杂度的算法可以参考归并排序的空间复杂度 5. 桶排序生成一些桶,让数字散列在不同桶中,对桶中元素分别执行排序,再将元素依次取出。 比如建立4个桶,遍历所有数字并依次分散到4个桶中,保证第2个桶所有数字都大于第1个桶,第3个桶所有数字都大于第2个桶。每个桶中分别进行选择排序,4个桶的元素都有序之后,再将元素依次取出。 123456789101112131415161718192021222324252627282930313233343536373839404142434445# 确定桶内排序方法(这里用选择排序)def SelectionSort(a): n = len(a) for i in range(n - 1): min = i for j in range(i + 1, n): if a[j] < a[min]: min = j a[i], a[min] = a[min], a[i]def BucketSort(a): # 确定列表元素最小值和最大值,定义桶数量 minV = min(a) maxV = max(a) bucketCount = 3 # 桶的数量 bucket = [[], [], []] # 计算每个桶的范围 perBucket = (maxV - minV + bucketCount) // bucketCount # 遍历列表每个元素,计算该元素放入的桶的索引,并且放入相应桶中 for num in a: bucketIndex = (num - minV) // perBucket bucket[bucketIndex].append(num) # 遍历每个桶,对每个桶的元素进行选择排序 for i in range(bucketCount): SelectionSort(bucket[i]) idx = 0 # 遍历每个桶,遍历桶中元素,将元素放回原列表 for i in range(bucketCount): for v in bucket[i]: a[idx] = v idx += 1 print(bucket) print(a) a = generate_random_sequence(10, 1, 100)print(a)BucketSort(a)'''输出结果:[84, 66, 53, 83, 22, 15, 95, 6, 32, 70][[6, 15, 22, 32], [53], [66, 70, 83, 84, 95]][6, 15, 22, 32, 53, 66, 70, 83, 84, 95]''' 桶的数量可以根据待排序元素的分布情况和排序的要求来决定,一般将待排序的元素均匀分配到桶中,桶内的排序算法可以是任意一种排序算法,取决于应用场景和性能要求。这个算法挺有意思的,可以看到每个桶的元素数量不一定一样,桶排序一般不会直接用,往往会做变形。 这个桶排序算法的时间复杂度为O(n + k^2),其中n是待排序元素的数量,k是桶的数量。具体来说,遍历列表并将元素放入桶中的时间复杂度为O(n),对每个桶进行选择排序的时间复杂度为O(k^2),遍历每个桶并将元素放回原列表的时间复杂度为O(n)。因此,总的时间复杂度为O(n + k^2),也就是O(n)。 空间复杂度方面,除了原始列表外,额外使用了一个大小为k的桶列表来存储元素。因此,空间复杂度为O(n + k),也就是O(n)。 6. 计数排序利用哈希表的思想,对数据类型和范围有要求。 首先生成一个计数器数组,并且一开始所有值的计数都为0,然后遍历枚举原数组的所有元素,在元素值对应的计数器上执行计数操作。最后遍历枚举计数器数组,按照数组中元素个数放回到原数组中,这样所有元素都是升序排列了。 1234567891011121314151617181920212223242526272829def CountingSort(a): n = len(a) # 获取原数组中最大值,加1,作为计数器列表的实际长度 cntlen = max(a) + 1 # 生成一个值都为0的计数器列表 cnt = [0] * cntlen # 遍历枚举原数组所有元素,在对应的计数器上加1 for val in a: cnt[val] += 1 print(cnt) n = 0 # 遍历枚举计数器列表,cnt[val]代表val这个数有多少个,大于0,则将它的计数器减一,并放到原来的列表中。如果还有则继续迭代至计数为0 for val in range(0, cntlen): while cnt[val] > 0: cnt[val] -= 1 a[n] = val n += 1a = generate_random_sequence(10, 1, 20)print(a)CountingSort(a)print(a)'''输出结果[8, 14, 18, 13, 16, 17, 7, 10, 8, 15] (原列表)[0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1] (计数器列表)[7, 8, 8, 10, 13, 14, 15, 16, 17, 18] (计数排序后的列表)''' 很明显,这种排序方式对数据有较大的限制,适用于数据范围较小的非负整数,且数据分布较均匀的情况(并不是说负数就完全不能用)。这很好理解,数据范围太广,会导致计数数组很大,占用大量内存空间,如果数据分布不均匀,计数的效率也会降低。 本质上来说这种计数排序算法是一种简单的桶排序算法,一个计数器就是一个桶。计数排序算法**时间复杂度是O(n),空间复杂度是O(n)**。 7. 基数排序和上面的计数排序很像,本质上也是桶排序,只不过用数位来划分桶。 首先建立0-9的10个桶,对待排序的每个元素,按照个位数的值放入对应的桶中,按顺序遍历桶中元素,取出来放回原数组。对于待排序的每个数字,按照十位数的值放入对应桶中,按顺序遍历桶中元素,取出来放回原数组。以此类推,按照百位数、千位数的值放入桶中,遍历,取出,直到排序完成。 123456789101112131415161718192021222324252627282930313233def RadixSort(a): base = 1 # base为取的数位 maxv = max(a) # 从低到高遍历每个数位 while base < maxv: bucket = [] # 每次遍历定义10个桶 for idx in range(10): bucket.append([]) # 每个原列表元素根据当前数位放入对应的桶中 for num in a: idx = num // base % 10 # //向下取整,%除法取余 bucket[idx].append(num) l = 0 # 遍历每个桶,按顺序放回原列表 for idx in range(10): for v in bucket[idx]: a[l] = v l += 1 print(a) base *= 10a = generate_random_sequence(10, 1, 1000)print(a)RadixSort(a)'''输出结果:[119, 13, 832, 247, 117, 126, 996, 904, 112, 396] (原序列)[832, 112, 13, 904, 126, 996, 396, 247, 117, 119] (按照个位数放入桶中,遍历取出)[904, 112, 13, 117, 119, 126, 832, 247, 996, 396] (按照十位数放入桶中,遍历取出)[13, 112, 117, 119, 126, 247, 396, 832, 904, 996] (按照百位数放入桶中,遍历取出)''' 上面的代码只适用于正整数,我们通过循环遍历每个数位,所以外层循环的次数是位数d。内层循环中,我们遍历了待排序数组并将每个元素放入对应的桶中,所以内层循环的时间复杂度是O(n)。最后,我们遍历每个桶,按顺序将元素放回原列表,这也需要O(n)的时间复杂度。 总的来说,基数排序的时间复杂度为O(d * (n + k)),其中d表示位数,n表示待排序数组的长度,k表示桶的数量。空间复杂度为O(dk+n)。 如果有负整数,可以把原数组元素分为正整数和负整数,分别进行基数排序后合并: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152def RadixSort(a): # 分离正数和负数 positive_nums = [num for num in a if num >= 0] negative_nums = [-num for num in a if num < 0] # 对正数部分进行基数排序 if len(positive_nums) > 0: base = 1 maxv = max(positive_nums) while base < maxv: bucket = [[] for _ in range(10)] for num in positive_nums: idx = num // base % 10 bucket[idx].append(num) positive_nums = [v for bucket_list in bucket for v in bucket_list] print(positive_nums) base *= 10 # 对负数部分进行基数排序 if len(negative_nums) > 0: base = 1 maxv = max(negative_nums) while base < maxv: bucket = [[] for _ in range(10)] for num in negative_nums: idx = num // base % 10 bucket[idx].append(num) negative_nums = [v for bucket_list in bucket for v in bucket_list] print(negative_nums) base *= 10 # 将负数部分反转 negative_nums = [-num for num in negative_nums[::-1]] print(negative_nums) # 合并正数和负数部分 sorted_a = negative_nums + positive_nums return sorted_aa = generate_random_sequence(10, -1000, 1000)print(a)a = RadixSort(a)print(a)'''输出结果:[-918, -574, 385, -322, -164, 880, -158, -400, 607, -746] (原序列)[880, 385, 607][607, 880, 385][385, 607, 880] (基数排序后的正数序列)[400, 322, 574, 164, 746, 918, 158][400, 918, 322, 746, 158, 164, 574][158, 164, 322, 400, 574, 746, 918] (基数排序后取反的负数序列)[-918, -746, -574, -400, -322, -164, -158] (反转的成原来的负数序列)[-918, -746, -574, -400, -322, -164, -158, 385, 607, 880] (整合排序后的结果)''' 8. 快速排序找到一个基准点,把小于它的和大于它的数分开,分别递归执行快速排序。也是用了分治的思想,属于冒泡排序的改进算法。 12345678910111213141516171819202122232425262728293031323334353637383940# 从a列表start到end之间寻找基准数下标,并且将所有小于等于它的数放在它左边,大于它的数放在右边def QuickSortPivot(a, start, end): pivot = start # 最左边数为基准数 j = start + 1 # j代表大于基准数的数的下标左边界 # 遍历列表所有数,如果当前数小于等于基准数,则a[i]和a[j]交换,j自增;大于则不处理(保证j下标以前的数小于等于基准数) for i in range(start + 1, end + 1): if a[i] <= a[pivot]: a[i], a[j] = a[j], a[i] j += 1 # 遍历之后,基准数与小于基准数的最后一个数交换(这样就可以让基准数左边和右边分开,且基准数位置就是正确的了) a[pivot], a[j - 1] = a[j - 1], a[pivot] # 更新基准数下标 pivot = j - 1 print(a[pivot], a[start : pivot], a[pivot + 1 : end + 1]) return pivot# 快速排序函数,用来对区间[start, end]的数递归执行快速排序def QuickSort(a, start, end): if start >= end: return # 获得基准数下标,分别递归计算左边和右边部分 pivot = QuickSortPivot(a, start, end) QuickSort(a, start, pivot - 1) QuickSort(a, pivot + 1, end)a = generate_random_sequence(10, 1, 100)print(a)QuickSort(a, 0, 9)print(a)'''输出结果:[55, 100, 41, 99, 52, 90, 50, 71, 92, 44] # 原序列55 [44, 41, 52, 50] [90, 99, 71, 92, 100] # 基准数,基准数左边序列,基准数右边序列44 [41] [52, 50]52 [50] []90 [71] [99, 92, 100]99 [92] [100][41, 44, 50, 52, 55, 71, 90, 92, 99, 100] # 快速排序后的序列''' 这种排序算法也有一个缺点,如果很不巧每次选取的基准点都是序列的最大值或者最小值,那么时间复杂度将会是最大值O(n^2)。 时间复杂度: 在每一次划分操作中,需要遍历待排序数组的所有元素,这需要O(n)的时间。 在每一次划分操作中,将数组划分为两个子数组,每个子数组的长度大约是原数组的一半(最好的情况)。因此,划分操作的时间复杂度为O(n)。 快速排序的递归深度为logn,因为每次划分操作都将数组的规模减半。 因此,最好的情况下时间复杂度为O(nlogn),最坏情况下时间复杂度为O(n^2)。 空间复杂度: 快速排序使用递归调用来对子数组进行排序,每次递归调用都需要保存当前的函数调用信息(包括参数、局部变量等)。 快速排序的递归深度通常为logn,因此需要的栈空间也是logn。 因此,总的空间复杂度为O(logn)。最坏的情况下是O(n),随机化基准值pivot可以防止最坏情况发生。 可以用随机快速排序,每次找基准点采用了一次随机,规避快速排序最坏的情况发生。 1234567891011121314151617181920212223242526272829303132333435363738394041import randomdef QuickSortPivot(a, start, end): # 引入区间中随机一个元素的索引值,和最左边的数交换(本来是最左边的数作为下一轮的基准数) randIdx = random.randint(start, end) a[start], a[randIdx] = a[randIdx], a[start] pivot = start j = start + 1 for i in range(start + 1, end + 1): if a[i] <= a[pivot]: a[i], a[j] = a[j], a[i] j += 1 a[pivot], a[j - 1] = a[j - 1], a[pivot] pivot = j - 1 print(a[pivot], a[start : pivot], a[pivot + 1 : end + 1]) return pivotdef QuickSort(a, start, end): if start >= end: return pivot = QuickSortPivot(a, start, end) QuickSort(a, start, pivot - 1) QuickSort(a, pivot + 1, end) a = generate_random_sequence(10, 1, 100)print(a)QuickSort(a, 0, 9)print(a)'''输出结果:[24, 2, 44, 75, 32, 32, 68, 36, 9, 79]68 [9, 2, 44, 32, 32, 24, 36] [75, 79]36 [9, 2, 32, 32, 24] [44]9 [2] [32, 32, 24]24 [] [32, 32]32 [32] []79 [75] [][2, 9, 24, 32, 32, 36, 44, 68, 75, 79]''' 9. 希尔排序本质是一种改进后的插入排序,又称“缩小增量排序”,增量在这里是指按照一定规则选择的间隔值(这里也是分组数),通常设置为数组长度的一半,每次缩小增量直到增量为1,组内排序方法为插入排序。每轮希尔排序的分组数越来越小,也就是说组内元素越来越多,最后一组就是整个数组。 123456789101112131415161718192021222324252627282930def ShellSort(a): n = len(a) # gap为增量,每隔gap值执行插入排序 gap = n // 2 while gap > 0: for i in range(gap, n): x = a[i] j =i while j >= gap: if x < a[j - gap]: a[j] = a[j - gap] else: break j -= gap a[j] = x print(a) # 增量除2向下取整,继续迭代 gap = gap // 2a = generate_random_sequence(10, 1, 100)print(a)ShellSort(a)'''输出结果:[20, 17, 75, 69, 34, 85, 38, 23, 57, 28] # 原序列[20, 17, 23, 57, 28, 85, 38, 75, 69, 34] # 第一次希尔排序,实际分了5组[20,85][17,38][75,23][69,57][34,28]并组内进行了插入排序[20, 17, 23, 34, 28, 57, 38, 75, 69, 85] # 第二次希尔排序,实际分了2组[20,23,28,38,69][17,57,85,75,34]并组内进行了插入排序[17, 20, 23, 28, 34, 38, 57, 69, 75, 85] # 第三次希尔排序,实际就是1组,所有组内元素插入排序''' 希尔排序的时间复杂度是根据增量序列的选择而变化的。在最坏情况下(原序列为逆序),时间复杂度是O(n^2);最好的情况下(原序列本身有序),时间复杂度是O(n)。平均情况下,希尔排序的时间复杂度可以达到O(nlogn)。 希尔排序的空间复杂度是O(1),即不需要额外的空间来存储数据。希尔排序是一种原地排序算法,它通过交换元素的位置来实现排序,不需要额外的辅助数组或链表。因此,希尔排序的空间复杂度是常数级别的。 10. 堆排序堆是一颗完全二叉树,假设一个节点编号为idx,则左子树树根编号为idx*2,右子树树根编号为idx*2+1,把每个结点的编号和顺序表下标对应,就可以把这颗二叉树用顺序表形式存储起来。 对于一个给定的顺序表,首先构造成大顶堆(所有根结点大于左右子结点),方法是从顺序表的最后一个元素开始,自底向上构造堆。对于某个元素,取左右子树中的大者和该元素比较,如果比该元素大,则与之交换(该元素所在位置下沉)。由于每个堆左右子树都是满足堆的性质的,当枚举完根结点后大顶堆就构造完毕了。 这个时候堆顶的元素是最大的,把堆顶元素和顺序表最后一个元素进行交换(弹出),最大值就在最后一位了。继续利用堆的下沉操作,除了最后一位以外继续构造大顶堆,把次大元素交换到倒数第二位,依此类推,最终整个顺序表就是升序排列的有序顺序表了。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748# 实现大顶堆下沉操作,heap整个顺序表start到end之间组成合法的堆,start为根结点坐标def maxHeapify(heap, start, end): son = start * 2 # 左子树根 # 左子树存在,则取左子树和右子树根中的大者的下标,存储到son中 while son <= end: if son + 1 <= end and heap[son + 1] > heap[son]: son += 1 # 如果结点点的值大于根结点的值,则将根结点和子结点交换(下沉),子结点迭代执行 if heap[son] > heap[start]: heap[start], heap[son] = heap[son], heap[start] start, son = son, son * 2 # 如果子结点的值小于等于根结点的值,说明堆构造没问题,跳出循环 else: break# 实现堆排序操作def HeapSort(a): # 堆下标从1开始,列表下标从0开始,所以在0的位置加上占位符None heap = [None] + a # 定义堆顶元素下标root为1 root = 1 l = len(heap) # 从顺序表l//2个元素开始(因为堆的最后一层是没有子结点的)逆序枚举,自底向上构造堆 for i in range(l // 2, root - 1, -1): maxHeapify(heap, i, l - 1) # 堆顶和最后一个元素交换,除最后一个元素外,重构堆 for i in range(l - 1, root, -1): heap[i], heap[root] = heap[root], heap[i] maxHeapify(heap, root, i - 1) print(heap[root:])a = generate_random_sequence(10, 1, 100)print(a)HeapSort(a)'''输出结果:[3, 52, 44, 78, 60, 100, 54, 61, 38, 1] # 原数列[78, 61, 54, 52, 60, 44, 3, 1, 38, 100] # 最大数为100,1-9位重新构造大顶堆[61, 60, 54, 52, 38, 44, 3, 1, 78, 100] # 次大数为78,1-8位重新构造大顶堆[60, 52, 54, 1, 38, 44, 3, 61, 78, 100] # 第三大数为61,1-7重新构造大顶堆[54, 52, 44, 1, 38, 3, 60, 61, 78, 100] .[52, 38, 44, 1, 3, 54, 60, 61, 78, 100] .[44, 38, 3, 1, 52, 54, 60, 61, 78, 100] .[38, 1, 3, 44, 52, 54, 60, 61, 78, 100] .[3, 1, 38, 44, 52, 54, 60, 61, 78, 100] .[1, 3, 38, 44, 52, 54, 60, 61, 78, 100] # 最后一次构造大顶堆,只有一个最小元素1,此时数列已经升序排好''' 堆排序的时间复杂度为O(nlogn),n是待排序元素的数量,空间复杂度为O(1),因为它是原地排序算法,不需要额外的空间来存储元素。 最后是一张来自菜鸟教程的排序算法总结图:","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"编程自学","slug":"编程自学","permalink":"http://www.shelven.com/categories/%E7%BC%96%E7%A8%8B%E8%87%AA%E5%AD%A6/"}],"tags":[{"name":"python","slug":"python","permalink":"http://www.shelven.com/tags/python/"}]},{"title":"群体基因组学——GWAS分析","slug":"群体基因组学——GWAS分析","date":"2023-06-20T10:40:24.000Z","updated":"2023-06-24T07:28:17.000Z","comments":true,"path":"2023/06/20/a.html","link":"","permalink":"http://www.shelven.com/2023/06/20/a.html","excerpt":"本篇笔记主要记录如何在linux命令行中用TASSEL5和PLINK2软件做GWAS分析,以及如何可视化GWAS结果(在R中绘制曼哈顿图和QQ图)。关于GWAS是做什么的,以及基本概念介绍可以看上一篇笔记介绍:群体基因组学——概述和图表详解 - 我的小破站 (shelven.com)","text":"本篇笔记主要记录如何在linux命令行中用TASSEL5和PLINK2软件做GWAS分析,以及如何可视化GWAS结果(在R中绘制曼哈顿图和QQ图)。关于GWAS是做什么的,以及基本概念介绍可以看上一篇笔记介绍:群体基因组学——概述和图表详解 - 我的小破站 (shelven.com) PLINK和TASSEL都是做GWAS分析用的经典软件,两个软件都有linux版本和windows版本,我这里是在linux集群环境中跑的分析流程,所有软件下载linux版本,两款软件可以在下方官网中安装: PLINK 2.0 (cog-genomics.org)、Tassel TASSEL官网有tassel pipeline命令行的帮助文档,一些常用的参数可以参考Tassel5PipelineCLI.pdf (bytebucket.org) TASSEL中文文档可以参考本站Tassel5中文操作手册.pdf 1. 数据格式plink的数据格式有两套,每套各自的前缀名称相同,一套后缀为.bed、.bin和.fam,另一套后缀为.map和.ped,后面的分析以第一套为例,读取速度比较快。 那么这三个是怎么来的呢?我们做群体重测序后,通过GATK等软件做变异检测最终会生成一个VCF文件,通过软件plink可以将VCF文件转化成上面的三个文件,分别展示一下三个文件的内容和结构: fam文件(家族信息文件)有6列,分别为家系编号、个体编号、父系编号(0表示信息缺失)、母系编号(0表示信息缺失)、性别编号(1表示男,2表示女,0表示未知性别)、表型值(1表示对照,2表示病例,0/-9或者其他非数字表示信息缺失) bim文件(个体信息文件)有6列,分别为染色体编号、SNP标识、以摩根或厘摩为单位的位置(可以用0)、碱基对坐标、Allele1和Allele2(通常是主效等位基因) bed文件为二进制文件 转化成这三个文件主要是为了下一步更快过滤数据,因为bed是二进制文件,计算机处理起来更快。 以上数据来源是贺师兄做的棉花重测序样本的部分变异信息文件。 2. 基因型数据清洗(使用PLINK)使用plink软件过滤掉最小等位基因频率(Minor Allele Frequency,MAF)小于0.05的变异位点,在关联分析中MAF值太小会造成假阳性。比如说,一个基因座上有两个等位基因A和B,A在群体中的频率为0.6,B在群体中的频率为0.4,那么MAF值就是0.4,可以想到一个基因座上MAF值越小,基因座上的等位基因越单一,MAF太低的位点贡献的信息很少,不与表型做关联分析。 1234567# 过滤MAF小于0.05的SNP,重新生成.bed、.bin和.fam文件## --make-bed 创建PLINK1二进制文件集plink --bfile Course_GWAS --maf 0.05 --make-bed --out Course_GWAS_maf0.05# 重新将三个过滤后的文件转化成vcf文件## --recode 创建新文件集,可选格式'vcf','vcf-fid',和'vcf-iid'plink --bfile Course_GWAS_maf0.05 --recode vcf-iid --out GWAS_vcf 可以通过查看过滤前后的bim文件,统计过滤了多少位点(plink软件在屏幕上的标准输出也会显示): 123456789101112131415161718192021222324# plink输出结果PLINK v2.00a5LM 64-bit Intel (7 Jun 2023) www.cog-genomics.org/plink/2.0/(C) 2005-2023 Shaun Purcell, Christopher Chang GNU General Public License v3Logging to Course_GWAS_maf0.05.log.Options in effect: --bfile Course_GWAS --maf 0.05 --make-bed --out Course_GWAS_maf0.05Start time: Tue Jun 20 14:38:58 20231837 MiB RAM detected, ~914 available; reserving 850 MiB for main workspace.Using up to 2 compute threads.216 samples (0 females, 0 males, 216 ambiguous; 216 founders) loaded fromCourse_GWAS.fam.10000 variants loaded from Course_GWAS.bim.Note: No phenotype data present.Calculating allele frequencies... done.3030 variants removed due to allele frequency threshold(s)(--maf/--max-maf/--mac/--max-mac).6970 variants remaining after main filters.Writing Course_GWAS_maf0.05.fam ... done.Writing Course_GWAS_maf0.05.bim ... done.Writing Course_GWAS_maf0.05.bed ... done. 需要注意下我这里使用的表型数据都是处理好的,所以只对基因型数据做过滤就行,如果自己有表型数据还要做一下表型数据清洗,比如删除异常值等等。 生成的GWAS_vcf.vcf用于下一步关联分析。 3. 关联分析(使用TASSEL5)PLINK软件也可以做关联分析,网上也能找到一把教程,但是PLINK只能做GLM模型的关联分析,现在文献中用的比较多的软件是TASSEL5,接下来演示用命令行的Tassel Pipeline做关联分析。 3.1 计算亲缘关系矩阵12345678# -Xmx10G 控制最大堆(heap size)的大小为10G# -importGuess 使用Tassel的importGuess函数载入文件# -KinshipPlugin 调用亲缘关系矩阵插件,与 -endPlugin 联用# -method 这里指定计算IBS亲缘关系矩阵# -export 指定输出文件,输出文件类型与input数据有关,比如基因型表默认Hapmap格式,距离矩阵默认SqrMatrix# -exportType 指定输出文件类型,有Hapmap, HDF5, VCF, Plink, SqrMatrix等等perl /opt/TASSEL5/run_pipeline.pl -Xmx10G -importGuess GWAS_vcf.vcf -KinshipPlugin -method Centered_IBS -endPlugin -export kinship.txt -exportType SqrMatrix 生成的216个样本的亲缘关系矩阵kinship.txt如下: 3.2 主成分分析123456# -fork<id> 用于区分不同的pipeline,代表一个pipeline的起始,后面可以是数字或者字符(非空格)# -PrincipalComponentsPlugin 调用主成分分析插件,同样和 -endPlugin 联用# -covariance true 计算协方差矩阵(用来识别主成分),两种方法,相关系数(correlation)或者协方差(covariance)# -runfork<id> 这个参数已经可以不需要了,pipeline会自动执行需要的forkperl /opt/TASSEL5/run_pipeline.pl -fork1 -importGuess GWAS_vcf.vcf -PrincipalComponentsPlugin -covariance true -endPlugin -export pca -runfork1 默认情况下会生成的PCA结果主要展示前5个主成分,且这一步会生成三个txt文件,PCA结果展示在第一个文件pca1.txt: 3.3 可视化亲缘关系矩阵和PCA结果(群体结构分析)在拿到这两个矩阵之后就可以通过R可视化结果,首先是亲缘关系矩阵热图: 1234567891011library(data.table)kinship = fread("kinship.txt",skip = 3) # 前3行不用读入setDF(kinship) # 将data.table格式转化成data.frame格式row.names(kinship) = kinship$V1kinship$V1 = NULL colnames(kinship) = row.names(kinship) # 第一列做行名,删除第一列,行名设置为列名 kinship = as.matrix(kinship) # 转成矩阵格式pdf("kinship.pdf")heatmap(kinship,labRow=F,labCol=F) # 绘制热图,不在行和列上显示标签dev.off() 用颜色深浅不同表示每个样本之间的亲缘关系远近,颜色越深亲缘关系越近。 PCA结果作图: 123456789library(data.table)library(ggplot2)pca_re = fread("pca1.txt",skip = 2)plot = ggplot(pca_re, aes(x=PC1, y=PC2)) + geom_point(size=2) + geom_hline(yintercept = 0) + # 添加x坐标 geom_vline(xintercept = 0) + # 添加y坐标 theme_bw()ggsave(plot =plot, filename="2D-PCA.pdf") # 以第一主成分为X轴,第二主成分为y轴作图即可 这里的群体结构差异有但不能很好分类。如果做群体结构分析的时候可以分成明显的几个不同的部分,那就要考虑后续做GWAS分析过程中要考虑群体结构的分层问题了。这里我们只是拿部分数据跑个简单的流程,继续往下。 3.4 基于GLM模型进行GWAS分析现在分别有了如下的基因型数据(GWAS_vcf.vcf)、表型数据(phenotype.tassel.txt),就可以以PCA结果作为协变量,基于GLM模型进行GWAS分析: 12345# -excludeLastTrait 移除表型数据的最后一列(不太明白什么意思?官网说可用于删除的最后一列用于MLM或GLM的群体结构)# -combine<id> 用在新的pipneline开头,将多个来自不同pipeline的数据集组合到一起,后面使用 -input<id> 指定,以 -intersect 结尾# -FixedEffectLMPlugin 使用GLM模型perl /opt/TASSEL5/run_pipeline.pl -fork1 -importGuess GWAS_vcf.vcf -fork2 -importGuess phenotype.tassel.txt -fork3 -importGuess pca1.txt -excludeLastTrait -combine5 -input1 -input2 -input3 -intersect -FixedEffectLMPlugin -endPlugin -export glm 主要结果为glm1.txt,结果如下: 我们主要用到的数据是: 第一列:表型 第二列:SNP名称 第三列:染色体编号 第四列:处于染色体的位置 第六列:p值 3.5 基于MLM模型进行GWAS分析在tassel中运行MLM模型和GLM模型相似,MLM模型需要亲缘关系矩阵来定义个体之间的关系,以PCA和kinship作为协变量,基于MLM模型进行GWAS分析: 123# -mlm -mlmVarCompEst P3D -mlmCompressionLevel Optimum 使用P3D和最优水平压缩perl /opt/TASSEL5/run_pipeline.pl -fork1 -importGuess GWAS_vcf.vcf -fork2 -importGuess phenotype.tassel.txt -fork3 -importGuess pca1.txt -fork4 -importGuess kinship.txt -combine5 -input1 -input2 -input3 -intersect -combine6 -input5 -input4 -mlm -mlmVarCompEst P3D -mlmCompressionLevel Optimum -export mlm 主要结果为mlm6.txt(前5个是5个表型的分析数据),结果如下: 我们主要用到的数据是: 第一列:表型 第二列:SNP名称 第三列:染色体编号 第四列:处于染色体的位置 第七列:p值 3.6 计算核酸多态性值1vcftools --vcf GWAS_vcf.vcf --site-pi --out pi 用vcftools就可以根据vcf文件统计平均每个位点的π值。 4. GWAS结果可视化1234567891011121314151617181920212223242526272829303132# qqman包绘制曼哈顿图和qq图library(qqman)library(data.table)glm <- fread("glm1.txt")[,c("Trait","Marker","Chr","Pos","p")]names(glm) <- c("Trait","SNP","CHR","BP","P")mlm <- fread("mlm6.txt")[,c("Trait","Marker","Chr","Pos","p")]mlm <- na.omit(mlm) # 需要注意mlm结果文件有一行是NaN值,去除names(mlm) <- c("Trait","SNP","CHR","BP","P") # 自定义作图函数plot_func <- function(data,trait,model){ pdf(paste(model,"_",trait,".manhttan.pdf",sep = ""), width = 11,height = 6) manhattan(data[Trait==trait,2:5], # 曼哈顿图需要的是"SNP","CHR","BP","P"这几列信息 suggestiveline = F, genomewideline = F) # remove the suggestive(Default -log10(1e-5)) and genome-wide(Default -log10(5e-8)) significance lines,也就是去掉两条阈值线 dev.off() pdf(paste(model,"_",trait,".qq.pdf",sep = "")) qqman::qq(data[Trait==trait,]$P) # qq图只要获得每个SNP位点的p值就可以作图 dev.off()}# 区分下不同表型for (trait in unique(glm$Trait)){ plot_func(glm,trait,"glm")}for (trait in unique(mlm$Trait)){ plot_func(mlm,trait,"mlm")} 生成各个表型GWAS分析的曼哈顿图和qq图: 随便查看一个MLM模型的GWAS曼哈顿图和qq图结果:","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"群体基因组学","slug":"群体基因组学","permalink":"http://www.shelven.com/categories/%E7%BE%A4%E4%BD%93%E5%9F%BA%E5%9B%A0%E7%BB%84%E5%AD%A6/"}],"tags":[{"name":"群体基因组学","slug":"群体基因组学","permalink":"http://www.shelven.com/tags/%E7%BE%A4%E4%BD%93%E5%9F%BA%E5%9B%A0%E7%BB%84%E5%AD%A6/"},{"name":"Tassel5","slug":"Tassel5","permalink":"http://www.shelven.com/tags/Tassel5/"},{"name":"Plink","slug":"Plink","permalink":"http://www.shelven.com/tags/Plink/"}]},{"title":"群体基因组学——概述和图表详解","slug":"群体基因组学——概述和图表详解","date":"2023-06-16T08:47:16.000Z","updated":"2023-06-16T08:57:50.000Z","comments":true,"path":"2023/06/16/a.html","link":"","permalink":"http://www.shelven.com/2023/06/16/a.html","excerpt":"最近看群体遗传学的文章,苦于不能理解研究者做的另人眼花缭乱的图表……哎,该补补基础了。这篇笔记先从樊龙江主编的《植物基因组学》教材开始学习基础概念,有部分摘自知乎和一些文献,加上这个领域论文图表的解读和自己的理解做汇总整理。下一篇笔记自己实操跑一下GWAS流程。","text":"最近看群体遗传学的文章,苦于不能理解研究者做的另人眼花缭乱的图表……哎,该补补基础了。这篇笔记先从樊龙江主编的《植物基因组学》教材开始学习基础概念,有部分摘自知乎和一些文献,加上这个领域论文图表的解读和自己的理解做汇总整理。下一篇笔记自己实操跑一下GWAS流程。 1. 群体基因组学概述群体基因组学,将基因组数据和技术与群体遗传学理论体系结合,通过覆盖全基因组范围的多态性推测全基因组效应和位点特异性效应。说人话,这门学科的作用就是通过检测SNP等的信息来回答关于物种起源、进化以及环境适应的分子机制。 群体基因组研究的基础是高质量的群体基因型数据,在获得群体原始测序数据后,如何高效准确的进行变异检测和群体基因分型是后续研究的关键(因为要用到大量的测序数据,所以也很烧钱)。 变异检测的流程可分为数据质控——读序联配——变异检测——基因分型 上图的bam文件通过软件GATK检测结构变异或者SNP就是变异检测,这部分内容我前面组装三代基因组测序数据中使用过,因为我只有一个二倍体基因组,所以当时也没用到基因分型,GATK用法可以看我的这篇笔记: 0基础学习三代基因组测序组装(9)——GATK检测植物基因组SNP和INDEL变异 - 我的小破站 (shelven.com) 2. 群体基因组进化分析方法2.1 群体系统发生树和群体结构群体发生树反映特定群体中个体间亲缘关系。一般我们做进化树都是用单拷贝基因,而做群体研究构建进化树用了整个基因组数据信息(比如全基因组SNP、Indel、SV等),能一定程度抵消横向基因转移和单个基因速率差异带来的分析误差,比单基因构建的结果更接近真实进化关系。全基因组SNP构建进化树(一般是把SNP merge到一起),个体间两两比较,根据SNP差异计算个体差异距离,构建群体差异矩阵。软件有MEGA、PHYLIP、FastTree等。 还有一个非常重要的概念:群体遗传结构,指基因型或者基因在空间和时间上的分布模式,包括种群内的遗传变异和种群间的遗传分化。种群遗传结构反映物种进化历史中的一些特殊进化事件,软件有Structure、Frappe等。总体流程确定亚群数(K),然后计算各个体归属第K亚群的概率Q值。以及主成分分析(PCA)也常用于群体结构划分。 上图是华农王茂军老师课题组做的不同二倍体棉花的物种发生树和群体结构。 2.2 群体遗传分化2.2.1 固定系数 Fst在明确一个物种存在群体结构后,通产需要量化该物种亚群间的分化程度。固定系数(Fst)反映群体等位基因杂合性水平,Fst越大,种群间遗传分化程度越大。 Fst= (Ht-Hs)/HtHs:亚群体中的平均杂合度Ht:复合群体中的平均杂合度 2.2.2 核酸多态性 𝝅做群体的文献中经常出现这个指标类似𝝅野生/𝝅栽培这样的系数,核酸多态性(Nucleotide diversity)是用于衡量群体的多态性的,为群体内平均每两个样本每个位点的差异核苷酸数量。一般称为𝝅,自然群体多态性一般高于栽培群体。 需要注意下这个𝝅的单位,一般都在 10^(-3) 这个数量级。 这些群体分化的相关系数计算可以用软件Arlequin、FSTAT、VCFtools等。 2.3 基因流(渐渗)基因流(也称为基因迁移)指从一个物种的一个种群向另一个种群引入遗传物质,从而改变群体的遗传组成。 当一个种群中的一个个体迁移到另一个群体,就会把基因带到新的群体,也就是产生“基因的流动”。基因流越大,群体间的相似性越大,会导致群体间基因频率和基因型频率呈现哈迪-温伯格平衡(在一个随机交配的大群体中,没有突变、选择等外界环境因素的影响,基因频率和基因型频率世代恒定)。 自然选择和遗传漂变会使群体之间的差异增加,而基因流的作用是“弱化”群体间的遗传差异,群体之间趋于一致。在植物中,种子扩散和花粉传播是植物基因流的最主要方式。从上面的描述也能看出,基因流与Fst成反比;与种群间地理距离成反比。检测方法主要有D-统计(ABBA-BABA)、Treemix、Migrate-N等。 2.4 种群动态和进化历史种群动态指种群大小或密度随时间或空间的变化。 有效群体大小(Ne)指与实际群体有相同基因频率方差或者相同杂合度衰减率的理想群体含量,通常小于绝对群体大小,决定群体平均近郊系数增量大小,反映群体遗传结构中基因的平均纯和速度。种群动态理论与方法主要包括种群大小及其变化、种群生长模式的量化描述,以及引起种群变化的外在环境因素。基于全基因组的种群历史动态分析方法主要有PSMC、MSMC等。 上图展示了不同地理来源的拟南芥的有效群体大小变化。可以看到拟南芥在9万~12万年前,由祖先群体在非洲开始分离形成亚群。欧亚祖先群体在 8 万年前从非洲分化形成,然后欧亚拟南芥群体在4万年前进一步分化。这一模式与包括人类在内的多种物种分化时间模式非常相似,这意味着间冰期和洪积世时期(即9万~12万年前)的气候事件对物种分布十分重要。 3. 基因组选择信号自然群体区别于栽培群体的最大特征是丰富的遗传多样性。草本作物祖先自然群体多态性(𝝅)一般在 0.003~0.008,高于相应的驯化作物遗传多态性。 遗传瓶颈效应:由于环境骤变或人类活动,某一生物种群的规模迅速减少,仅有少部分个体能够顺利通过瓶颈事件,之后可能经历一段恢复期并产生大量后代。由于连锁不平衡(LD)的作用,不仅仅是驯化基因的多态性降低,其侧翼区域遗传多样性也会降低。 有害性突变(deleterious SNP,dSNP)指导致生物个体的整体适合度(fitness)减低的突变。植物被驯化的过程中,基因组上有害突变的数量、频率会不断增加和累积,导致驯化后的作物在原本自然环境中适合度降低,称为作物的“驯化成本”。 在作物群体基因组中,一个明显的特征是存在大量来自野生祖先种的遗传渐渗。来自野生群体的基因渗入可以有效增加栽培种的环境适应性。 选择分析可以在宏观和微观两个尺度下进行选择基因和信号鉴定。 3.1 宏观进化尺度 宏观进化尺度 基于基因:Ka/Ks(非同义替换比同义替换)、MKT检验(比较物种内和物种间的Ka/Ks值) 基于进化速率:HKA 宏观尺度检测选择信号的方法,通常在亲缘关系相近的物种之间进行同源基因序列的比较,判断基因是否在某一物种或进化分支上存在加速进化的情况。 判断加速进化的背景指标是Ka/Ks,比较每个位点区域中非同义替换率与每个位点的同义替换率。同义突变总体是中性进化的(中性基准),如果存在过多的非同义突变意味着存在倾向于蛋白序列改变的正向选择(正选择表现为固定某种有利等位基因,负选择表现为清除不利的等位基因)。MKT检验本质是比较物种内和物种间的Ka/Ks值,中性情况下相等,如果物种间的比值显著高于物种内比值,意味着物种中存在正向选择信号,如果是在作物驯化中,可能是人工选择的信号。 3.2 微观进化尺度 微观进化尺度 基于等位基因频谱:Tajima’s D, Fu andLi’sD, Fay and Wu’sH, CLR, Hp 基于连锁不平衡:LRH、iHS、连锁不平衡衰减(LDD)、IBD分析 基于群体分化:LKT、LSBL 组合方法:CLR、XP-CLR(适合SNP大数据集)、DH检验、CMS 微观尺度鉴定方法,在正向选择的作用下,有利等位基因在群体中频率会变高甚至固定。当有利等位基因和周围变异达到较高频率的时候,这段区间在群体水品就会呈现遗传多态性降低的现象,也就是选择性清除(selective sweep)。 比如,栽培物种在选择性清除区域的遗传多样性显著降低,就是驯化区域的典型特征。选择性清除还会使连锁不平衡区块延长、群体间固定指数增大、Tajima‘s D值为负且显著偏离零。中性模型为基础的检测方法很多,Tajima‘s D检验是最经典的。 总体来说,选择信号检测原理和方法可以分为以下5类: A 基于群体多态性。原理:受选择位点两侧序列多态性因连带效应保持很低水平。在驯化选择区间内,𝝅野生/𝝅栽培的值变高。 B 基于等位基因频谱。原理:有利突变不断在群体中固定,当完全固定时,周围可能由于随机突变出现稀有等位基因 C 基于连锁不平衡。原理:选择性清除会引起包含等位基因的单倍体纯和性提高,与周围变异的连锁不平衡程度变高。当群体处于正向选择作用下时,基因突变及其连锁位点在正选择的作用下,在短时间内会达到较高频率,形成大片段的纯合单倍型。 D 基于群体分化。随着受选择的等位基因频率上升,导致与原群体的Fst变大。Fst的取值范围为0-1,1表示群体间完全分化的位点,0表示在群体间完全没有分化的位点。 E 组合方法。 在检测选择信号时,需要考虑遗传漂移(genetic drift)对选择信号的影响,为了降低该影响,通常在全基因组扫描窗口的基础上,仅考虑极端值(前5%)区域作为选择位点(这点很重要,后面实操的时候会说)。 选择信号检测方法都是基于自下而上的,通过群体基因组扫描鉴定具有选择信号的基因组区域,从而挖掘可能与某种表型或者适应性相关联的基因,假阳性高。 也可以用自上而下的方法进行佐证,比如获得该物种多个时间点或者多个世代的群体基因组数据,就可以直接检测该位点等位基因频率变化加以验证,比如用QTL、GWAS等传统定位方法。 4. 连锁不平衡(LD)连锁不平衡理论对群体研究非常重要,前面说到基于连锁不平衡的原理检测选择信号,这里再加深一下理解。 连锁不平衡(linkage disequilibrium,LD):在某一群体中,两个基因同时遗传的频率大于随机组合的频率。 连锁不平衡的三个衡量指标: D值:D = P(AB) - P(A) X P(B),也就是实际概率减去独立遗传的理论概率,D值绝对值越大,连锁程度越大。 D‘值:理解维归一化之后的D值,归一化之的值可以用于比较不同基因连锁程度的大小。0表示完全连锁平衡,独立遗传;1表示完全连锁不平衡。 r平方 r值定义如下: 通常文献里也是用r平方来表征连锁不平衡程度,r平方等于0时,表示完全连锁平衡,独立遗传;r平方等于1时, 表示完全连锁不平衡。 这里就要引出另一个很重要的概念——LD衰减,也是做GWAS出现最多的图。 4.1 LD衰减曲线LD衰减指位点间由连锁不平衡到连锁平衡的演变过程;LD衰减的速度在不同物种间或同物种的不同亚群间差异非常大。所以,通常会使用“LD衰减距离”来描述LD衰减速度的快慢。 衰减距离,是当平均LD系数衰减到一定大小的时候,对应的物理距离。(这个一定大小是没有特别规定的,比如可以到一半大小,可以到0.5,可以到0.1) 上面的图描绘了不同水稻群体的LD衰减曲线,横坐标是物理距离(kb),纵坐标是LD系数(r^2)。 LD衰减曲线就是利用曲线图来呈现基因组上分子标记间的平均LD系数随着标记间距离增加而降低的过程。大概的计算原理就是先统计基因组上两两标记间的LD系数大小,再按照标记间的距离对LD系数进行分类,最终计算出一定距离的分子标记间的平均LD系数大小。 我们看左边的图,Japonic这个亚群(红色的线)在基因组100Kb距离的平均LD系数大小为0.35,到了200kb距离,对应的LD系数降低到了不到0.3。LD衰减速度在不同亚群是不一样,Japonica衰减速度最慢(其他亚群在物理距离很小的时候LD系数降就从0.35衰减到了0.3),衰减距离最大。 知道这个衰减速度快慢和衰减距离有什么用呢? LD衰减速度越慢,形成的单体型区块越大,关联分析中需要的群体和标记数目越少,但定位越不精准。此外,同一个连锁群上,LD衰减地慢说明该群体受到选择,一般来说,野生群体比驯化改良群体LD衰减快,遗传多样性高。而驯化选择会导致群体遗传多样性下降,位点间的相关性(连锁程度)加强。所以,驯化程度越高,选择强度越大的群体,LD衰减速度越慢。 也就是说左边这个图的4个水稻品种中,Japonic是驯化程度最高的。 LD衰减距离应用: 辅助分析进化和选择,同一个连锁群上,LD衰减地慢说明该群体受到选择。 一般而言,两个位点在基因组上离得越近,相关性就越强,LD系数就越大。反之,LD系数越小。也就是说,随着位点间的距离不断增加,LD系数通常情况下会慢慢下降。 一般而言,LD系数大于0.8就是强相关。如果LD系数小于0.1,则可以认为没有相关性。如果LD衰减到0.1这么大的区间内都没有标记覆盖的话,即使这个区间有一个效应很强的功能突变,也是检测不到关联信号的。所以,通常可以通过比较LD衰减(到0.1)距离和标记间的平均距离,来判断标记是否对全基因组有足够的覆盖度。 如果GWAS检测到显著关联的区间后,则可以通过进一步绘制局部的LD单倍型块图(这个后面讲),来进一步判断显著相关的SNP和目标基因间是否存在强LD关系。 基因组上那些受选择的区域相比普通的区域,LD衰减速度也是更慢的。 判断GWAS所需标记量,决定GWAS的检测效力以及精度; GWAS标记量 = 基因组大小 / LD衰减距离。 4.2 单倍型块(Haplotype Block)描述LD不平衡的另一个常用的图就是LD单倍型块图。 单倍型块,即连锁不平衡区域,是指同一条染色体上处于连锁不平衡状态的一段连续的区域。单倍型块分析可以用于筛选tag SNP、确定候选基因的范围等。 颜色从白色到红色,代表连锁程度从低到高,方框中的数值为LD系数r^2,为了美观,这里将 r2^2乘以了100。 上面的图中每个正方形是两个相邻SNP的LD值计算结果,如果大于设定的阈值(这里估计是大于94),那么就构成一个block,图中黑框为一个block,共有三个。可以从三个block中分别选择一个SNP作为Tag SNP来分别代表这三个block。 5. 全基因组关联分析(GWAS)全基因组关联分析(Genome-Wide Association Study, GWAS),以连锁不平衡(linkage disequilibrium, LD)为基础,通过分析数百个或者数千个个体的高密度分子标记的分离特征(一般是上万个或者上百万个SNP标记),筛选与复杂性状表现型变异相关联的分子标记,进而分析分子标记对表现型的遗传效应。 插一句题外话,GWAS中最后一个字母已经是study,在写英文文章的时候不要写成GWAS study…… 传统的QTL定位研究通常以2个亲本杂交群体为研究对象, 通过连锁作图定位目标性状位点。局限性是投入大量资源构建数量庞大的重组群体。而关联分析则可以利用个体在全基因组范围的遗传变异标记进行多态性检测,获得更高分辨率的定位结果(单碱基水平), 遗传变异来源也更为广泛,根据统计量或者显著性P值筛选最有可能影响该性状的遗传变异,往往能定位到比双亲本作图群体中更多的性状关联位点(这可节省太多时间了)。 GWAS的局限性在于可以确定相关位点但不能直接确定基因本身,假阳性也比较高。解决这一局限性有两种策略,一是算法,二是群体的选取。 5.1 GWAS算法模型目前用的算法模型有以下几种: 1.GLM (Generalized linear model;一般线性模型) 2.MLM (Mixed linear model;混合线性模型) 3.GEMMA、CMLM、SUPER、FarmCPU、Blink 主要介绍前两个,GLM模型以群体结构矩阵 Q或主成分分析矩阵PCs为协变量做回归拟合。 如果两个表型差异很大,但群体本身还含有其他的遗传差异(如地域等),则那些与该表型无关的遗传差异也会影响到相关性。MLM模型可以把群体结构矩阵 Q、亲缘关系矩阵(kinship)或联合利用主成分分析矩阵PCs和亲缘关系矩阵为协变量,把这种位点校正掉(也就是抑制假关联)。 其他模型大多是基于GLM和MLM进行优化,比如GEMMA 计算个体基因型相似性矩阵,排除了LD的干扰。其他暂时就不多说了,感兴趣再深入研究研究。 5.2 GWAS群体选取群体中丰富的表型变异和充分的遗传重组是GWAS 成功的关键条件,有以下选取策略: (1) 群体内没有明显的群体结构,样本间没有过近的亲缘关系,同时具有丰富的表型变异 (2) 群体来自具有一定水平遗传分化的不同类群(如不同亚种与亚群),具有丰富的遗传和表型变异,但同时不同类群之间存在频繁的遗传交流,保证目标性状在不同类群内部也存在一定水平的变异 目前GWAS样本量普遍大于100份 前面也说过,同时要考虑GWAS标记量,计算公式GWAS标记量 = 基因组大小 / LD衰减距离。 在用GATK做基因分型的时候也要注意,结果一般保留maf值大于0.05的 SNP,通常认为这是在驯化选择区间内。 5.3 GWAS结果图解还是先说明一下,做GWAS需要有三类数据:SNP数据、表型数据和群体结构。这些数据在实操会再次提到。 GWAS的结果通常以曼哈顿图和QQ图来展示。曼哈顿图显示每个SNP在关联分析中的显著性水平; QQ 图反映关联分析的效果。 以下图721份水稻GWAS结果图为例: 5.3.1 曼哈顿图(Manhattan Plot)图4(A)就是一个曼哈顿图,每个点代表一个SNP,x轴代表SNP在基因组上的遗传位置,y轴为–log10 (P-value),显著性水平线有红蓝两条(P = 0.01和P= 0.05)。y轴值越大,说明该位点表型的关联程度越大,受到连锁不平衡(LD)的影响,基因组上强关联位点周围的SNP也会呈现出关联性由高到低连续变化的信号强度,从而在P值小的地方出现尖峰(peak)。 上面文章深入分析水稻6号染色体上与抽穗期相关的一个尖峰,将其定位在Hd3a附近, 估计候选区间大概在 2.68–4.62 Mb (图4C)。这个候选区间是如何确定的呢? 我们通常将显著关联SNP在N kb以内的位置确认为相关区间,这个N就是对应的LD衰减距离。确定GWAS鉴定的候选区间后,就可以在候选区间内找到基因功能注释和表型相关的基因,或者有其他组学或者功能研究支持的基因进行验证和深入研究。 5.3.2 QQ图(QQ Plot)图4(B)是QQ图,本质上就是做两组数据的比较,判断是否一致。每个点代表一个SNP,横坐标是期望的P-value,纵坐标为实际观测到的P-value,虚线代表实际观测和期望的P-value值一致的情况。 我们做关联分析,当然是希望大部分的位点观测值和期望值一样(在对角线,也就是这里的虚线上),也就是这部分位点确定是与性状不关联的。一部分位点在虚线的上方,也就是观测值超过期望值,说明这些位点的效应超过随机效应,也就是这些位点是与性状显著相关的。也就是出现4(B)这个图才是理想的结果。 还有以下三种其他情况: 1 观察值低于期望值(点在对角线下方),可能是模型不合理,P-value被过度矫正。或者是群体中大量SNP位点间存在连锁不平衡,有效位点数(相互间不存在连锁不平衡的位点)明显低于实际位点数,所以P-value的期望值被低估了。 2 观察值和期望值相同,说明没有找到与性状显著关联的位点。 3 观察值显著高于期望值(点全都在对角线上方),也就是所有位点都与某个性状显著相关,说明分析模型不合理,假阳性太多了。 现在基于GWAS分析还衍生出其他诸如TWAS(基因表达量做标记)、PWAS(蛋白做标记)、EWAS(甲基化表观信息做标记)等关联分析方法,做标记的信息不同,本质上也都是和表型做关联分析。 6. 泛基因组(Pan-Genome)想了想还是把泛基因组也放了进来,虽然泛基因组研究方法和上面的研究方法不一样,但也是群体基因组学的一个重要分支,主要是开展群体基因组重测序和遗传变异的分析工作。 我们知道单一的参考基因组仅能代表物种基因组空间范围的一小部分(同一个物种不同亚群基因组存在差异),以线性参考基因组进行群体变异的分析就会错失一些变异位点(SV&CNV&PAV)。所以这个时候我们就需要获取一个物种的全部遗传信息(只能说是相对的全部),并解析每个个体间的遗传变异,这就诞生了泛基因组(Pan-Genome)研究。 6.1 核心基因/非核心基因在泛基因组中有两个非常重要的概念: 1 核心基因(core genome):在物种所有个体内都存在的,保持生命体基本功能,代表物种基本表型特征。 2 非必需基因(dispensable genome):使不同个体在表型,生活方式、代谢等多方面发生明显的分化,最终表现为丰富的遗传多样性。 上图可以看出核心基因和非必需基因的区别,以及最后融合成pan-genome。主要注意一点,核心基因是这个物种中所有个体都存在的,但不一定是必须基因,必须基因的概念比核心基因窄,核心基因包含了必须基因。 现在的pan-genome分析的文章一般还会对基因集做更进一步的细分: core 核心基因,所有样本共享 softcore 次核心基因,90%样本共享 dispensable 非必需基因,多个样本共享但 < 90% private 特有基因,1%,或者仅存在于一个样本中 对不类型基因集进行保守性分析,有助于挖掘适应性进化或驯化中发挥关键作用的基因。文章中用的比较多的还有核心基因/非必需基因与重复序列相关性分析、表达水平分析等等,这些展开来说太多了,具体文章具体分析。 6.2 泛基因组组装泛基因组组装方式现在见的比较多的是两种: 6.2.1 基于二代测序(迭代组装) How the pan-genome is changing crop genomics and improvement | Genome Biology | Full Text (biomedcentral.com) 将多个样本二代下机数据与参考基因组比对,未比对上的reads组装成新的contigs,将这些contigs添加到原始参考序列中(一般直接放在最后),最终获的物种的泛基因图谱。 看得出来这种方式简单粗暴,可以快速得到泛基因组信息(也省钱),但是有二代数据的通病——如果物种的基因组比较大,或者测序深度不够深的话,组装的contigs连续性会比较差。 6.2.2 基于三代测序(从头组装&图形泛基因组) Plant pan-genomes are the new reference | Nature Plants 对多个个体进行从头组装、注释,从全基因组层面识别变异信息,也是现在用的最广泛的方法。不依赖参考基因组,但是需要较高的测序深度(保证准确性),组装到染色体水平。图形泛基因组是在从头组装的基础上,基于图论的组装方法,利用有向图将物种基因组分为核心基因组和非必需基因组。 6.3 泛基因组分析主要可以分以下两部分: 这部分内容留到以后我做泛基因组组装实操再做详细解释和补充(先挖个坑)。 下篇笔记将简单实操一下GWAS分析,使用软件为TASSEL。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"群体基因组学","slug":"群体基因组学","permalink":"http://www.shelven.com/categories/%E7%BE%A4%E4%BD%93%E5%9F%BA%E5%9B%A0%E7%BB%84%E5%AD%A6/"}],"tags":[{"name":"群体基因组学","slug":"群体基因组学","permalink":"http://www.shelven.com/tags/%E7%BE%A4%E4%BD%93%E5%9F%BA%E5%9B%A0%E7%BB%84%E5%AD%A6/"}]},{"title":"三维基因组学——如何用Hi-C数据分析拓扑关联结构域","slug":"三维基因组学——如何用Hi-C数据分析拓扑关联结构域","date":"2023-06-14T16:24:17.000Z","updated":"2023-06-16T08:49:30.000Z","comments":true,"path":"2023/06/15/a.html","link":"","permalink":"http://www.shelven.com/2023/06/15/a.html","excerpt":"本篇笔记主要记录三维基因组学的学习,以及演示如何利用Hi-C数据分析Compartment和拓扑关联结构域(TAD),所使用的分析Hi-C数据的软件为HiC-Pro,可视化软件为HiCPlotter和R包HiTC。","text":"本篇笔记主要记录三维基因组学的学习,以及演示如何利用Hi-C数据分析Compartment和拓扑关联结构域(TAD),所使用的分析Hi-C数据的软件为HiC-Pro,可视化软件为HiCPlotter和R包HiTC。 1. 三维基因组简介首先什么是三维基因组学?三维基因组学(Three-Dimensional Genomics, 3D Genomics),是指在考虑基因组序列、基因结构及其调控元件的同时,对基因组序列在细胞核内的三维空间结构,及其对基因转录、复制、修复和调控等生物过程中功能的研究。 随着基因组学和表观基因组学的发展,基因组学研究的维度经历了从一维到三维的转变: 一维:基因组序列的测定与组装 二维:调控序列/转录因子—基因互作网络、染色质状态、表观修饰 三维:基因组的三维空间结构 基因组不是简单的线性序列,是有三维空间结构的,而且这种三维空间结构可以对DNA辅助、基因转录调控、染色质浓缩分离等生物学过程产生重要的影响。 我们知道,真核生物的染色质结构由低级到高级可以分为4种: 一级结构:一系列核小体相互连接成的念珠状结构 二级结构:由核小体连接起来的纤维状结构经螺旋化形成中空的螺线管 三级结构:由螺线管螺旋化形成的筒状结构,称为超螺线管 四级结构:超螺线管进一步螺旋折叠形成染色单体 三维基因组的层级结构类似于真核生物的染色质结构,在不同的分辨率下也可以划分为4个层级结构: 染色质环(Chromatin loops),分辨率:<1 Kb - few Mb:染色质在空间中形成的环状结构,使相距很远的染色质区域在三维空间中可以聚集在一起。 拓扑关联结构域(Topologically associating Domains, TAD),分辨率:40 Kb - 3 Mb:相互作用相对频繁的染色质区域。 染色质区室(A/B compartments),分辨率:1 - 100 Mb:A compartments:开放的染色质、表达活跃、基因丰富、较高的GC含量、激活型的组蛋白标记,通常位于细胞核内部;B compartments:关闭的染色质、表达不活跃、基因缺乏、结构紧凑、沉默基因的组蛋白标记,通常位于细胞核外围。 染色质疆域(Chromosome Territory, CT),分辨率:~100 Mb:在真核生物中,细胞核内染色质分布并不是随机的,为了跨越较大距离实现相互作用,这些染色质会在三维空间中靠的更近,这就是染色质疆域。 不同层次分辨率下的研究重点不同,比如最小的loop层次对应的是基因级别甚至更小的元件互作;10kb级别一般就可以鉴定TAD之间的互作关系(也是研究比较多的);更大一些的染色质区室也是研究比较多的内容;在染色质疆域这个层次主要就是研究染色体之间的互作关系了。相应的,分辨率越高,需要的有效互作数据量也越大。 2. 三维基因组技术目前三维基因组结构的检测时基于染色体构象捕获技术,也就是3C(chromosome conformation capture)技术,3C是所有染色体构象捕获技术的基础。根据染色质的互作类型,3C技术又可以大致分为5种方法: 1 versus 1:3C的由来,经过酶切、DNA片段绑定、反向交联后,通过qPCR确认互作位点。 1 versus Many/All:4C技术,3C技术的升级版。反向交联前和3C一样,经过了第二轮消化和绑定,通过特定位点的反向PCR检测特定位点与全基因组潜在位点互作情况。 Many versus Many:5C技术,基于3C的另一升级版。5C技术的通量增大。 Many versus All:Capture-C技术,最大不同是在互作片段对的捕捉技术上,它是利用生物素标记反向互补到限制酶酶切位点,从而进行对所有感兴趣的互作位点和全基因组位点之间互作对之间的捕捉。 All versus All 其中“1”“Many”以及“All”代表的是在一次实验中所涉及的位点,比如说 “1 versus All” 的意义就是该次实验调查的是一个位点和全基因组中所有可能潜在互作位点之间的互作情况。 近年来all versus All也就是检测全局的互作技术发展比较快,有以下一些技术应用: Hi-C技术。近年最火的全基因组 3D 基因组测序技术,不仅可以用于检测全基因组的三维基因组结构和染色质互作,同时还可以用于辅助基因组组装(同样用的很多,现在组参考基因组都要测HiC,以组装到染色体水平)等。 ChIA-PET技术( chromatin interaction analysis paired-end tag sequencing):与3C基础上发展的其他技术不同,区别在于第一步对于互作位点的 DNA 破碎不是通过限制酶进行消化,而是利用超声波击碎。然后应用抗体对特定蛋白参与的互作区段进行富集,并对这些互作区段进行消化连接,提取含有接头的双端序列( paired end tag, PET)进行互作检测。 DLO Hi-C技术( digestion-ligation-only Hi-C):该技术相对于传统的全基因组染色体构象捕获技术 Hi-C 而言更加高效简单,仅需要两轮的消化连接过程,无须生物素标记,未连接的 DNA 也可以被有效地去除,极大地提高了染色体构想捕获效率。 DLO Hi-C 技术更像是 Hi-C 技术的一个升级优化。 原位Hi-C (in situ Hi-C):使用完整的细胞核进行后续反应,减少了Hi-C中随机连接造成的背景噪音。使用四碱基酶Mbol进行酶切,提高了分辨率,实验时间由Hi-C的7天缩短至3天。 HiChIP (in situ Hi-C followed by chromatin immunoprecipitation):该技术是一种利用原位Hi-C原理和转座酶介导构建文库来解析染色质构象的方法。 3. 利用Hi-C数据分析拓扑关联结构域(TAD)现在Hi-C测序是应用最广泛的三维基因组学技术,对Hi-C数据的处理,构建Hi-C互作图谱和鉴定TAD结构域是最关键的问题,也发展了一大批专门的生物信息学算法软件,我这里用经典的软件HiC-Pro、HiCPlotter和R包HiTC跑一遍Hi-C数据分析的流程。 为了快速得到结果,这里选择了Gossypium hirsutum四倍体陆地棉的两条染色体参考序列,和一部分Hi-C测序结果(50万条reads的双端测序结果)跑下流程。后续制作Hi-C互作矩阵,和分析Compartment和TAD的.bed后缀文件和.matrix后缀文件来自尤师姐(前面的这点数据做不出互作图,太少了,跑完所有数据又很慢……)。 两个软件安装过程就不说了,可以参考github官方,如果HiC-Pro很难安装依赖的话可以用官方提供的singularity镜像: nservant/HiC-Pro: HiC-Pro: An optimized and flexible pipeline for Hi-C data processing (github.com) 当前路径文件结构如下: 3.1 获得染色质互作矩阵(HiC-Pro)12345678# 1.准备酶切片段## dpnii是选择的限制性核酸内切酶,可以用别的,查看digest_genome.py源码python digest_genome.py Gh_genome.fa –r dpnii –o Gh_dpnii.bed# 2.统计酶切片段大小awk ’{print $3-$2;}’ Gh_dpnii.bed > Gh_dpnii_size.txt# 3.构建参考基因组索引,生成基因组大小的文件samtools faidx Gh_genome.faawk ‘{print $1”\\t”$2;}’ Gh_genome.fa.fai > Gh_size.txt 这里酶切参考基因组生成的bed文件如下所示: 123456789101112131415161718192021222324252627282930313233343536373839404142# 4.修改config文件## 主要修改基因组索引文件路径、基因组大小文件路径、酶切片段文件路径和分辨率## BOWTIE2_IDX_PATH、GENOME_SIZE、GENOME_FRAGMENT、BIN_SIZEvim config.txt######################################################################### Alignment options#######################################################################FORMAT = phred33MIN_MAPQ = 0BOWTIE2_IDX_PATH = /home/Bioinfor/Gh_indexBOWTIE2_GLOBAL_OPTIONS = --very-sensitive -L 30 --score-min L,-0.6,-0.2 --end-to-end --reorderBOWTIE2_LOCAL_OPTIONS = --very-sensitive -L 20 --score-min L,-0.6,-0.2 --end-to-end --reorder######################################################################### Annotation files#######################################################################REFERENCE_GENOME = GhGENOME_SIZE = /home/Bioinfor/Gh_size.txt######################################################################### Digestion Hi-C#######################################################################GENOME_FRAGMENT = /home/Bioinfor/Gh_dpnii.bedLIGATION_SITE = AAGCTAGCTTMIN_FRAG_SIZE = 100MAX_FRAG_SIZE = 160000MIN_INSERT_SIZE = 200MAX_INSERT_SIZE = 600######################################################################### Contact Maps#######################################################################BIN_SIZE = 5000 20000 100000MATRIX_FORMAT = upper 主要是修改以上内容,但也要注意下Hi-C双端测序文件的后缀,要让软件能匹配上。 12# 5.运行HiC-Pro,获得染色质互作矩阵HiC-Pro -c config.txt -i Gh/ -o HiC_Pro_out HiC-Pro的主要结果放置在目录HiC_Pro_out/ hic_results/matrix/Gh/下,为5000、20000、100000 分辨率下的_abs.bed 以及_iced.matrix后缀文件。其他结果文件和分析图片可以在hic_results文件夹里查看,这里不展示了。 主要展示后续用的文件,以下为Gh_5000_abs.bed文件: 这个文件将染色体划分为5000 bp的bin,并且在第四列进行编号。 以下为Gh_5000_iced.matrix文件: 这个文件第一列和第二列都是bin编号,第三列为两个bin之间归一化之后的互作强度。 3.2 绘制染色质互作图(HiCPlotter)这里使用的是尤师姐提供的跑完了A01染色体的Gh_20000_iced.matrix与Gh_20000_abs.bed文件(我的示例Hi-C数据量太少)。 1234567891011# HiCPlotter绘制染色体互作图python HiCPlotter.py -f Gh_20000_iced.matrix -o Ghir_A01 -r 20000 -tri 1 -bed Gh_20000_abs.bed -n Ghir_A01 -chr Ghir_A01## HiCPlotter绘制染色质互作几个常用参数:-chr 染色体名称,如果染色体名称不是Chr*,-chr参数需传入对应的值-o 输出文件名-tri 默认值为0,1代表着传入的matrix和bed文件是HiC-Pro运行的结果-r 分辨率-n 任务名# ZModem协议传输文件sz Ghir_A01-Ghir_A01.ofBins\\(0-5887\\).20K.png 染色体Ghir_A01内部的互作图Ghir_A01-Ghir_A01.ofBins(0-5887).20K.png如下所示。 3.3 鉴定TAD(HiCPlotter)这里使用的数据与3.2一样。 12345678910# HiCPlotter鉴定TADpython HiCPlotter.py -f Gh_20000_iced.matrix –bed Gh_20000_abs.bed –o TAD -r 20000 -n Ghir_A01 -chr Ghir_A01 -tri 1 -fh 0 -s 600 -e 900 -ptd 1 -pi 1## HiCPlotter绘制TAD几个常用参数:-ptd 默认0,输入1,调用绘制TAD算法-fh 输入文件中抬头需要删除的行数,没有header lines就是0-s 起始位点(第几个bin)-e 结束位点(第几个bin)# ZModem协议传输文件sz TAD-Ghir_A01.ofBins\\(600-900\\).20K.png 染色体Ghir_A01的12Mb到18Mb之间TAD鉴定结果图TAD-Ghir_A01.ofBins(600-900).20K.png如下: 3.4 鉴定染色质区室和TAD(HiTC包)这里使用的数据来自尤师姐提供的LG02_50000_abs.bed与LG02_50000_iced.matrix文件,也是HiC-Pro跑出来的结果文件。 因为用到了R包,集群R有点问题,我暂时传到本地用自己电脑跑了下: 12345678910111213141516# 下载和加载HiTC包> BiocManager::install("HiTC")> setwd("D:/zhuomian/D5_50Kb/D5_50Kb")> library(HiTC)# 导入数据(importC函数读取HiC-pro的结果)> mydata <- importC("D:/zhuomian/D5_50Kb/D5_50Kb/LG02_50000_iced.matrix", xgi.bed = "D:/zhuomian/D5_50Kb/D5_50Kb/LG02_50000_abs.bed")# 主成分分析和鉴定Compartment> pc <- pca.hic(mydata$LG02LG02, npc = 1, asGRangesList = TRUE)> write.csv(pc, file = "D:/zhuomian/D5_50Kb/D5_50Kb/LG02_50k.csv")> mydata2 <- read.table("D:/zhuomian/D5_50Kb/D5_50Kb/LG03_50k.csv",header = TRUE,sep = ",")> barplot(mydata2$score)# 热图展示> mapC(mydata$LG02LG02, trim.range = 0.94, col.pos = c("white", "red")) 这里主成分分析得到的LG02_50k.csv,统计第一主成分score做条形图就可以鉴定染色质区室Compartment A/B了,以下链接是提出者的文章。 https://www.ncbi.nlm.nih.gov/pmc/articles/PMC2858594/ 跑出来的互作热图如下: 也可以选取染色体的一部分(一般是自己感兴趣的部分),做热图: 12> data1 <- extractRegion(mydata$LG02LG02,chr = "LG02", from = 1000, to = 5000000)> mapC(data1, trim.range = 0.94,col.pos = c("white", "red")) 关于如何用HiTC包分析Hi-C数据,也可以看bioconductor的一篇文章: Analyzing Hi-C data with the HiTC BioC package (bioconductor.org)","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"三维基因组学","slug":"三维基因组学","permalink":"http://www.shelven.com/categories/%E4%B8%89%E7%BB%B4%E5%9F%BA%E5%9B%A0%E7%BB%84%E5%AD%A6/"}],"tags":[{"name":"HiC-Pro","slug":"HiC-Pro","permalink":"http://www.shelven.com/tags/HiC-Pro/"},{"name":"HiCPlotter","slug":"HiCPlotter","permalink":"http://www.shelven.com/tags/HiCPlotter/"},{"name":"HiTC","slug":"HiTC","permalink":"http://www.shelven.com/tags/HiTC/"}]},{"title":"单细胞转录组分析","slug":"单细胞转录组分析","date":"2023-06-13T14:06:51.000Z","updated":"2023-06-16T08:50:07.000Z","comments":true,"path":"2023/06/13/a.html","link":"","permalink":"http://www.shelven.com/2023/06/13/a.html","excerpt":"最近因为学校网络信息中心升级防火墙,导致集群无法访问公网,我搭建的反向代理服务器也暂时无法使用(真的想吐槽学校的网络管理员,三个星期了还没能解决集群的网络问题)……近期要进行中期答辩,先用向日葵远程一下实验室闲置的电脑应急,顺便把最近的学习笔记补上。 这篇笔记主要记录下单细胞组学的学习,以及单细胞转录组(Single-cell RNA-sequencing,scRNA-seq)的分析流程。","text":"最近因为学校网络信息中心升级防火墙,导致集群无法访问公网,我搭建的反向代理服务器也暂时无法使用(真的想吐槽学校的网络管理员,三个星期了还没能解决集群的网络问题)……近期要进行中期答辩,先用向日葵远程一下实验室闲置的电脑应急,顺便把最近的学习笔记补上。 这篇笔记主要记录下单细胞组学的学习,以及单细胞转录组(Single-cell RNA-sequencing,scRNA-seq)的分析流程。 1. 单细胞测序发展我们做植物高通量测序做的最多的是RNA-seq,比较不同组织、不同时期或者不同处理下同一个组织基因的差异表达。我们做RNA-seq是建立在对一个组织的细胞进行测序的基础上,而组织是几类细胞的集合,因此我们得到的基因表达量是所有细胞的平均值。 单细胞测序可以在单个细胞的水平上构建细胞图谱,让我们更深入了解植物组织的细胞类型,获取每个细胞的转录本信息,研究细胞的发育动态(比如细胞如何进行分化)等等。 Nature杂志每年都会总结每个领域最有价值的年度技术,2018年是单细胞转录组技术,2019年是单细胞多组学技术,2020年是空间转录组技术,可以看出带有单细胞和空间分辨率转录组技术应用的重要性。 上图是单细胞技术在植物中的研究历程,最后一个2022年的Stereo-seq也就是华大自研的时空组学技术,据华大发表在cell上的文章Spatiotemporal transcriptomic atlas of mouse organogenesis using DNA nanoball-patterned arrays描述,Stereo-seq的技术参数优于当前其他空间转录组技术,对空间异质性的描述更为灵敏和直观。 对于Stereo-seq我个人的理解是补上了部分空间转录组测序无法做到单个细胞分辨率的短板,真正做到对动植物的组织或者器官中的所有细胞进行转录组信息分析,鉴定不同类型细胞的差异基因表达,构建空间图谱来分辨不同细胞的发育轨迹。感兴趣可以看下面华大的这篇scStereo-seq技术用于拟南芥的文章:The single-cell stereo-seq reveals region-specific cell subtypes and transcriptome profiling in Arabidopsis leaves - ScienceDirect 2. 单细胞测序平台 10x Genomics Chromium 微流控芯片技术获得单细胞反应体系,并在传统文库构建的基础上引入标签,通过追溯标签序列将众多mRNA、表面蛋白信息定位回原来的单个细胞。捕获效率最高达65% BD Rhapsody™ Single-Cell Analysis System 能为单细胞中每个转录本标记特异性分子标签,实现单细胞水平上基因表达谱的绝对定量。捕获效率最高达80% Illumina® Bio-Rad® Single-Cell Sequencing Solution 捕获效率低,仅为3%,但测序成本相对较低 ICELL8 Single-Cell System 捕获效率为30%,成本相对较低 C1™ 单细胞全自动制备系统 通量低、成本高、周期慢、对试验人员要求高,操作较繁琐和困难 3. 单细胞测序分析流程 质控部分和RNA-seq没有区别,去掉低质量的读序和测序接头。10X Genomics平台测的单细胞数据需要通过Cell Ranger回比到参考基因组,示例用法如下: 12345678 cellranger count --id=sample345 --transcriptome=/opt/refdata-cellranger-GRCh38-3.0.0 --fastqs=/home/jdoe/runs/HAWT7ADXX/outs/fastq_path --sample=mysample# --id 文件名# --transcriptome 参考基因组# --fastqs 转录组数据所在的路径# --sample 指定要使用的样本 这里主要记录下细胞过滤到亚群定义和标记基因筛选所要用到的软件Seurat4。因为我自己没有这方面的实验数据,仅仅只是尝试跑下流程,以下分析流程和代码来自: 单细胞转录组|Seurat 4.0 使用指南 - 知乎 (zhihu.com) 《Seurat 4 R包源码解析》 总目录 | 单细胞转录组分析标准流程 - 知乎 (zhihu.com) 示例数据来自10X Genomics免费提供的外周血单核细胞(PBMC)数据集:https://cf.10xgenomics.com/samples/cell/pbmc3k/pbmc3k_filtered_gene_bc_matrices.tar.gz 将示例数据上传至集群,解压,得到如下文件: 123├── barcodes.tsv(10X Genomics测序的与细胞相关的barcode信息,用于标识细胞)├── genes.tsv(基因信息,包括了基因组数据库人类登记号和基因名称)└── matrix.mtx(基因表达量矩阵) 这三个文件实际上要通过软件Cell Ranger对10X Genomics单细胞测序的下机数据进行比对基因组,统计捕获的细胞数、测序量得到。详细可以参考官方Cell Ranger软件的用法和结果分析,这里用的是官方整理后的数据,只进行下一步Seurat分析的演示。 以所在目录为工作目录,在linux中键入R进入交互命令行,运行Seurat。 3.1 导入数据和初步过滤123456789101112# 安装和加载Seurat和dplyr(用于数据清洗)R包BiocManager::install("Seurat") BiocManager::install("dplyr")library(dplyr)library(Seurat)# 读入数据并初步筛选pbmc.data <- Read10X(data.dir="/path/to/yourfile")# 创建Seurat对象,过滤检测少于200个基因的细胞,和少于3个细胞检测出的基因pbmc <- CreateSeuratObject(counts = pbmc.data, project = "pbmc3k", min.cells = 3, min.features = 200)pbmc 可以看到初步过滤后,现在的一个数据集包含了2700个细胞的13714个基因。 3.2 细胞过滤123456789101112131415161718# 新增一列percent.mt用于统计映射到线粒体基因组的reads百分比pbmc[["percent.mt"]] <- PercentageFeatureSet(pbmc, pattern = "^MT-")# 绘制细胞的三种特征,nFeature_RNA(每个细胞测到的特异基因数目)、nCount_RNA(每个细胞测到所有基因的表达量之和)、percent.mt的小提琴图png("fig01_cell_feature.png")VlnPlot(pbmc, features = c("nFeature_RNA", "nCount_RNA", "percent.mt"), ncol = 3)dev.off()# 细胞特征间的相关性绘图png("fig02_cell_feature_correlation.png", width=1000, height=400) plot1 <- FeatureScatter(pbmc, feature1 = "nCount_RNA", feature2 = "percent.mt")plot2 <- FeatureScatter(pbmc, feature1 = "nCount_RNA", feature2 = "nFeature_RNA")CombinePlots(plots = list(plot1, plot2))dev.off()# 根据特殊基因的数目以及线粒体基因比例过滤细胞,这里选取200 < nFeature_RNA < 2500 和percent.mt < 5的数据pbmc <- subset(pbmc, subset = nFeature_RNA > 200 & nFeature_RNA < 2500 & percent.mt < 5) pbmc fig01_cell_feature.png如下所示: 这里可以看到有的细胞检测到的特异基因数特别多,可能是因为多个细胞被同时捕获了,而特异基因数特别少的可能是低质量细胞。而线粒体基因比例特别高的细胞,有可能是一些快要死亡的细胞。这些低质量的细胞需要在这一步被过滤。 fig02_cell_feature_correlation.png如下所示: 这两个图分别表示了每个细胞测到所有基因的表达量之和与线粒体基因组百分比呈负相关,每个细胞测到所有基因的表达量之和与每个细胞测到的特异基因数目呈正相关。 这一步过滤后数据集剩下2638个细胞,共13714个基因。 3.3 细胞群体聚类1234567891011121314151617# 表达水平标准化## normalization.method 标准化方法,默认为“LogNormalize”## scale.factor 比例因子,用以计算标准化值,默认为“10000”pbmc <- NormalizeData(pbmc, normalization.method = "LogNormalize", scale.factor = 10000)# 鉴定mark基因(也就是特征基因)## selection.method mark基因筛选方法,“vst”为通过方差与均值比进行筛选## nfeatures 要筛选的mark基因的数目pbmc <- FindVariableFeatures(pbmc, selection.method = "vst", nfeatures = 2000)top10 <- head(VariableFeatures(pbmc), 10)# 展示mark基因png("fig03_10marker_genes.png",width=1000,height=400)plot1 <- VariableFeaturePlot(pbmc)plot2 <- LabelPoints(plot = plot1, points = top10, repel = TRUE)CombinePlots(plots = list(plot1, plot2))dev.off() fig03_10marker_genes.png如下所示: 这里筛选出2000个mark基因用于后续的下游分析,并且展示区分能力最强的前10个mark基因 123456789101112# Mark基因权重标准化## ScaleData 缩放基因的表达,给予基因同等的权重,使高表达基因不占据主导地位all.genes <- rownames(pbmc)pbmc <- ScaleData(pbmc, features = all.genes) # PCA主成分分析## RunPCA 计算特征值,在细胞间具有高度表达差异的基因有助于区分不同类型的细胞## 采用DimPlot方法可视化pbmc <- RunPCA(pbmc, features = VariableFeatures(object = pbmc))png("fig04_PCA.png")DimPlot(pbmc, reduction = "pca")dev.off() fig04_PCA.png如下所示: 12345678910111213# 评估主成分维度## num.replicate 重采样次数## dims 维度范围pbmc <- JackStraw(pbmc, num.replicate = 100) pbmc <- ScoreJackStraw(pbmc, dims = 1:20)## 评估主成分维度的方法1png("fig05_PC_importance_01.png",width=700,height=400)JackStrawPlot(pbmc, dims = 1:15)dev.off()## 评估主成分维度的方法2png("fig05_PC_importance_02.png",width=700,height=400)ElbowPlot(pbmc)dev.off() R包 Seurat 函数 JackStraw 、ScoreJackStraw进行重采样测试,评估用以进行细胞聚类的主成分维度。 fig05_PC_importance_01.png如下所示: fig05_PC_importance_02.png如下所示: 上图是比较不同主成分(PC)的P值分布与均匀分布(虚线);显著的主成分具有较低的P值(位于虚线上方);似乎10-12个主成分后,主成分的重要性急剧下降。 下图是肘部法则以确定最佳主成分数量,9-10个主成分附近有个拐点,表明大部分真实信号是在前10个pc中捕获的。 所以这里选择10个主成分用于后续分析。 12345678# 细胞群体聚簇pbmc <- FindNeighbors(pbmc, dims = 1:10) pbmc <- FindClusters(pbmc, resolution = 0.5) ## 绘图pbmc <- RunUMAP(pbmc, dims = 1:10) png("fig06_cluster_UMAP.png",width=700,height=400) DimPlot(pbmc, reduction = "umap")dev.off() fig06_cluster_UMAP.png如下所示 可以看到,根据mark基因的表达,所有细胞最终被分为了8种类型。 做完以上分析之后,还可以根据自己的研究目标新型不同的下游分析,具体来说可以筛选亚群的标记基因(与其他类别细胞的差异表达基因)、进行细胞的发育轨迹分析、做不同品种间细胞类型的比较等等。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"单细胞转录组","slug":"单细胞转录组","permalink":"http://www.shelven.com/categories/%E5%8D%95%E7%BB%86%E8%83%9E%E8%BD%AC%E5%BD%95%E7%BB%84/"}],"tags":[{"name":"Seurat","slug":"Seurat","permalink":"http://www.shelven.com/tags/Seurat/"}]},{"title":"基因组共线性分析——MCScanX","slug":"基因组共线性分析——MCScanX","date":"2023-04-20T15:55:19.000Z","updated":"2023-05-08T13:55:51.000Z","comments":true,"path":"2023/04/20/a.html","link":"","permalink":"http://www.shelven.com/2023/04/20/a.html","excerpt":"因为自己的生信基础知识比较薄弱,最近在华农跟着王老师上了一些植物基因组课程,记录一下。这一次主要结合课堂内容、MCScanX官方文档以及自己的理解,演示基因组共线性工具MCScanX的用法。","text":"因为自己的生信基础知识比较薄弱,最近在华农跟着王老师上了一些植物基因组课程,记录一下。这一次主要结合课堂内容、MCScanX官方文档以及自己的理解,演示基因组共线性工具MCScanX的用法。 1. 共线性分析在樊龙江主编的《植物基因组学》中提到,植物起源于水生藻类,不同植物在基因水平上具有一定保守性(也就是具有一定的相同基因)。在一定的亲缘关系内,这种基因组水平上的保守性(基因组区块排列顺序保守性),也就是不同植物基因组间的共线性。 当我们拿到两个植物基因组数据,想要分析两个物种间是否存在基因进化历史、染色体结构变异、重要功能基因的插入缺失或者鉴定全基因组复制事件的时候,就可以利用一些基因组共线性分析工具进行直观的作图和分析。 做共线性分析之前需要区分两个描述基因组共线性的名词: Synteny:两个物种的一组基因位点,在每个物种中位于同一条染色体(顺序不一定相同)。 Collinearity:两个物种的一组基因位点,分别位于各自的同一条染色体上,并且顺序也是一致的。 这里染色体打了个重点,因为所有的共线性分析都是基于染色体水平的基因组进行比较的,只有contig数据分析的话意义不大。现在的共线性研究以及开发的工具一般用的是collinearity(包括后面要说的MCScanx)。 共线性分析的原理主要都是分为三步: 获得基因在染色体上的位置(如基因组注释得到的gff文件)。 将基因的CDS/蛋白序列进行比对,得到高度相似的基因对(Anchoring)。 鉴定相同基因排列顺序的共线性区块(Chaining),不同软件算法不一样,这里不讨论算法只讨论如何应用。 其他就不过多介绍了,接下来主要讲讲MCScanX软件的用法。 2. 下载和编译MCScanX按照官网克隆仓库,编译即可。MCScanX github官网 123git clone https://github.com/wyp1125/MCScanX.gitcd MCScanXmake 编译之后可以看到主文件的MCScanX、MCScanX_h和Duplicate_gene_classifier三个核心分析程序,以及downstream_analyses下游分析文件夹中的12个与下游分析有关的java和perl文件。 3. 运行MCScanX3.1 数据预处理MCScan支持的输入文件有两个: m8输出格式的BLASTP比对结果文件 记录染色体、基因名称以及起始和终止位点4个信息的gff文件 这里以棉花基因组和近缘物种可可基因组为例,两者都可以在NCBI上找到蛋白序列和注释的gff3文件。由于上机课中给的蛋白序列和gff文件都是处理好的,如果自己处理gff文件,总体思路是从gff文件的第3列提取’gene’关键词,从第9列分离基因名称信息,保留第1列染色体信息,保留第4列和第5列保留start和end信息即可。 这里输入的gff文件不是标准格式,简单写个python脚本处理: 1234567891011import osimport pandas as pdfile_path = './Theobroma_cacao.chr.gff3'with open(file_path, "r") as file: temp = pd.read_csv(file, sep = '\\t',comment = '#', header = None) # 制表符分隔,#号为注释标识,无列名 temp = temp.drop(temp[temp[2] != 'gene'].index) # 第3列不为gene的行的索引,drop()删除 temp = temp[[0,8,3,4]] temp[8] = temp[8].map(lambda x: x.split(';')[0].split('=')[1].split(':')[1]) # 对第9列的处理,只取geneid temp.to_csv('Tcacao.gff3', header = None, index = None, sep = '\\t') 当然,根据官网的表述,最好将染色体名称改为两个字母(物种缩写) + 数字(代表染色体编号)的形式。不改也没关系,两个基因组的染色体名字不要一样就好了,不改的话只有在下游分析对共线性区块分组的时候有点影响(添加的物种那列只显示两个字母)实质上不会有什么影响。 这步检查一下gene数量与pep文件的蛋白质条数是否一样,不一样的话可能是pep中有转录本蛋白序列,重新筛选完整的gff3文件,再用gffread提取蛋白序列,这里就不赘述了,如果处理有问题后续在更新。 本例中,棉花Gossypium herbaceum相关文件前缀为Gh;可可Theobroma cacao相关文件前缀为Tcacao。 12345678910# 合并蛋白序列(方便做基因组组内和组间共线性分析)cat Gh.pep Tcacao.pep > Gh_Tcacao.pep# blast建库makeblastdb -in /public/home/wlxie/biosoft/MCScanX/Data/ExerciseData/Gh_Tcacao.pep -dbtype prot -input_type fasta -out Gh_Tcacao# 蛋白序列拆分40份(方便提交并行任务到集群)perl fasta-splitter.pl --n-parts 40 Gh_Tcacao.pepmkdir Gh_Tcacaomv Gh_Tcacao.part* Gh_Tcacao 用到的蛋白序列拆分perl脚本来自于Kirill Kryukov,具体在哪个仓库找不到了……这里提供下载,拆分后文件夹名称如下: 上面拆分的40个蛋白序列名称数字部分是等宽的,不方便提交并行任务到集群,这里简单修改下文件名称。 12345678910111213141516171819import os # 获取目录下所有文件列表path = '/public/home/wlxie/biosoft/MCScanX/Data/ExerciseData/Gh_Tcacao/'fileList = os.listdir(path)# 设置待修改的前缀和后缀prefix = 'Gh_Tcacao_'suffix = '.pep'# 批量修改文件名m = 1for file in fileList: old_path = path + os.sep + file if os.path.isdir(old_path): continue new_path = path + os.sep + prefix + str(m) + suffix os.rename(old_path, new_path) m += 1 修改之后提交40个blastp并行任务,每个任务4核,很快就可以跑完。 1234567#!/bin/bash#SBATCH --array=1-40#SBATCH --cpus-per-task=4echo start on $(date)srun blastp -query Gh_Tcacao${SLURM_ARRAY_TASK_ID}.pep -out Gh_Tcacao_${SLURM_ARRAY_TASK_ID}.blast -db Gh_Tcacao -outfmt 6 -num_threads 4 -num_alignments 5 -evalue 1e-10echo end on $(date) 最后合并blastp结果。 12cat Gh_Tcacao_*.blast > Gh_Tcacao.blastrm Gh_Tcacao_*.blast 3.2 运行MCScanX和结果解读MCScanX有三个主命令: MCScanX共线性分析 MCScanX_h也是共线性分析,输入不是BLASTP文件,而是第三方检测的以制表符分隔的成对同源关系文件 Duplicate_gene_classifier使用MCScanX的算法鉴定singleton(单基因)和重复基因 这里用MCScanX,前面处理好了的blastp结果文件和gff文件放在同一个文件夹,输入的文件相对路径要到文件名的前缀部分 1./MCScanX Data/ExerciseData/Gh_Tcacao 结果文件如下: .collinearity 是两个基因组共线性结果文件(默认分析collinearity),包含了本次运行的参数、计算出的共线性区块等。还可以根据这个文件用grep的方法提取两个物种之间的同源基因(提取第二列第三列物种名字不一样的行,去重复,计数),这里也不赘述了。 .html 这个文件夹每个文件对应一条染色体共线性分析结果,包括基因座的共线性区块数量、基因名称(串联重复基因会标红)和具体比对上哪些共线性区块: .tandem文件显示基因组内所有串联重复基因对: 3.3 下游分析和作图官方在downstream_analyses文件夹中提供了12个下游分析的perl和java脚本。我个人将这个文件下的分析工具分为两类: 功能相关(此部分与数据处理相关) 12345678910111213141516# Detect_syntenic_tandem_arrays 检测串联重复基因../../../downstream_analyses/detect_collinear_tandem_arrays -g Gh_Tcacao.gff -b Gh_Tcacao.blast -c Gh_Tcacao.collinearity -o tandem_arrays# Dissect_multiple_alignment 将共线性区块分为物种内和物种间共线性区块../../../downstream_analyses/dissect_multiple_alignment -g Gh_Tcacao.gff -c Gh_Tcacao.collinearity -o dissect_multiple_alignment# add_ka_and_ks_to_collinearity.pl 计算ka和ks值(在collinearity结果后面增加两列,执行时间较长)cat Gh.cds Tcacao.cds > Gh_Tcacao.cds # 需要cds序列,且与collinearity文件中的序列名要一致,否则算出来全部都是-2perl ../../../downstream_analyses/add_ka_and_ks_to_collinearity.pl -i Gh_Tcacao.collinearity -d Gh_Tcacao.cds -o add_kaks_to_synteny# group_collinear_genes.pl 基因分组,构建基因家族(结果文件需要去掉第一行无效信息)perl ../../../downstream_analyses/group_collinear_genes.pl -i Gh_Tcacao.collinearity -o group_collinear_genes# 以及以下几种功能就不一一试了# detect_collinearity_within_gene_families.pl 检测基因家族的共线性基因对,需要用到前面构建的基因家族文件# origin_enrichment_analysis.pl 根据Duplicate_gene_classifier的结果识别输入基因家族的重复基因起源的潜在富集?不太懂什么意思 作图相关(此部分均有对应的配置文件,在downstream_analyses文件夹操作,其他文件路径java会发生未知错误) 1234567# dot_plotter 两组染色体所有共线性区块做点图java dot_plotter -g ../Data/ExerciseData/Gh_Tcacao/Gh_Tcacao.gff -s ../Data/ExerciseData/Gh_Tcacao/Gh_Tcacao.collinearity -c dot.ctl -o dot.png# dot.ctl配置文件内容如下800 //x轴维度(像素大小,下同,不再赘述)800 //y轴维度Ghir_A01,Ghir_A02,Ghir_A03,Ghir_A04,Ghir_A05,Ghir_A06,Ghir_A07,Ghir_A08,Ghir_A09,Ghir_A10,Ghir_A11,Ghir_A12,Ghir_A13 //x轴染色体名称Chromosome_1,Chromosome_2,Chromosome_3,Chromosome_4,Chromosome_5,Chromosome_6,Chromosome_7,Chromosome_8,Chromosome_9 //y轴染色体名称 1234567# dual_synteny_plotterjava dual_synteny_plotter -g ../Data/ExerciseData/Gh_Tcacao/Gh_Tcacao.gff -s ../Data/ExerciseData/Gh_Tcacao/Gh_Tcacao.collinearity -c dual_synteny.ctl -o dual_synteny.png# dual_synteny.ctl600 //图宽800 //图高Ghir_A01,Ghir_A02,Ghir_A03,Ghir_A04,Ghir_A05,Ghir_A06,Ghir_A07,Ghir_A08,Ghir_A09,Ghir_A10,Ghir_A11,Ghir_A12,Ghir_A13 //位于左边的染色体名称Chromosome_1,Chromosome_2,Chromosome_3,Chromosome_4,Chromosome_5,Chromosome_6,Chromosome_7,Chromosome_8,Chromosome_9 //位于右边的染色体名称 12345# circle_plotterjava circle_plotter -g ../Data/ExerciseData/Gh_Tcacao/Gh_Tcacao.gff -s ../Data/ExerciseData/Gh_Tcacao/Gh_Tcacao.collinearity -c circle.ctl -o circle.png# circle.ctl800 //图宽和高Ghir_A01,Ghir_A02,Ghir_A03,Ghir_A04,Ghir_A05,Ghir_A06,Ghir_A07,Ghir_A08,Ghir_A09,Ghir_A10,Ghir_A11,Ghir_A12,Ghir_A13,Chromosome_1,Chromosome_2,Chromosome_3,Chromosome_4,Chromosome_5,Chromosome_6,Chromosome_7,Chromosome_8,Chromosome_9 //环状图的染色体名称 1234567# bar_plotterjava bar_plotter -g ../Data/ExerciseData/Gh_Tcacao/Gh_Tcacao.gff -s ../Data/ExerciseData/Gh_Tcacao/Gh_Tcacao.collinearity -c bar.ctl -o bar.png# bar.ctl800 //x轴维度800 //y轴维度Ghir_A01,Ghir_A02,Ghir_A03,Ghir_A04,Ghir_A05,Ghir_A06,Ghir_A07,Ghir_A08,Ghir_A09,Ghir_A10,Ghir_A11,Ghir_A12,Ghir_A13 //参考染色体Chromosome_1,Chromosome_2,Chromosome_3,Chromosome_4,Chromosome_5,Chromosome_6,Chromosome_7,Chromosome_8,Chromosome_9 //目标染色体 123# 还有两个作图的工具也不一一试了,以后有需要再做# family_circle_plotter 基因家族同源基因圆形图,红线连接一个基因家族所有同源基因# family_tree_plotter 基因家族树,共线性基因对用红线连接,串联重复基因用蓝线连接 可以看到这个软件绘图还是非常简单方便的,不过图片质量不是很好,作图的像素点大小都是由自己定义的,因此排版缩放也很容易失真。还是比较推荐JCVI一类的软件,引入多种过滤参数功能更强大,且做的图是矢量图,比较好看……而且有docker镜像,不怕安装依赖的问题: 1singularity -d build jcvi.sif docker://tanghaibao/jcvi 以后有空再更新吧~ 2023.5.8 更新上面例子仅仅只是作图,没有阐明具体的生物学问题。这里通过以上的数据补充几个思考: 棉花和可可的WGD(全基因组加倍)事件分别在多少年前? 在多少年前棉花和可可发生了物种分化? 在解决上面两个问题前,需要知道一个基本概念——中性进化论。 在中性进化理论中,分子水平的变异是中性的,不受自然选择的影响。也就是说对于一个基因序列而言,每个位点上的演化(也就是发生突变)速率都是恒定的,如果一个基因位点发生同义突变,氨基酸序列并未发生变化,则这种突变不会影响物种的适应性。 同义突变率ds(Ks)指平均每个同义位点上发生同义置换的数目,在物种进化中代表了进化过程的背景碱基替换率,两个物种或者同个物种之间的Ks值可以通过上面的MCScanX的下游分析程序add_ka_and_ks_to_collinearity.pl计算得出。 如果一个物种发生了全基因组加倍事件,则会产生大量的旁系同源基因,反映在Ks值上就是有大量的Ks值接近的同源基因对产生,在Ks统计图中会出现一个峰(peak);如果两个物种之间发生了物种分化,同样产生大量的直系同源基因(物种形成的伴随事件),同样是Ks值接近的同源基因产生,并且在Ks统计图出现峰值。因此,我们可以根据同一个物种内以及不同物种间的同源基因Ks值来反推物种的WGD事件和物种分歧事件,Ks峰值处发生的事件即为最近一次的物种WGD事件或物种分歧事件。 这些事件发生的时间点,如果有已知的化石证据则最准确(根据放射性同位素衰变),如果没有就需要根据分子钟理论计算对应的时间,我们用最基础的公式T=Ks/2r。Ks,即同义突变率,平均每个位点的突变次数;r是核酸突变速度,也就是这个分类的物种每个位点每年的突变概率。 解释一下这个公式怎么来的,在两个物种分化一定的时间T后,两者都以相似的速度r累积突变(分化后是近缘物种,核酸突变速度类似),则两个物种之间核酸替换率K=T*(r+r)。实际上为了避免进化选择对突变速率的影响,这里一般用同义突变率替代核酸替换率。r值是怎么计算的呢?同样要依赖化石证据,根据两个物种共同祖先的化石时间反推r值,假设同一类物种核酸突变率类似,则这个r值还可以进一步用于别的近缘物种。 以上理论依据建立在同一类物种核酸突变率类似的假设中,实际不一定如我们所愿,且化石依据也会存在一定误差。这个r值也只是估计个大概,实际上只要在10的-9次方数量级,算出来的时间能自圆其说就行。 12345# 利用MCScanX计算物种内部的共线性结果./MCScanX -b 1 Data/ExerciseData/Gh_Tcacao_1# 利用MCScanX计算两个物种之间的共线性结果./MCScanX -b 2 Data/ExerciseData/Gh_Tcacao_2 分别整理棉花与棉花、可可与可可、棉花与可可的基因组共线性结果,只选取最后一列ks值,用R做Ks密度分布图,可以参考现有的教程: Ks密度曲线分布图绘图 - 简书 (jianshu.com) Finding Peak Values For a Density Distribution (ianmadd.github.io) 找到Ks密度分布图的峰值,选一个参考文献里合适的r值,就可以计算上面两个问题了。 顺便补充一下,下面这篇教程里有一个python脚本可以提取最长转录本,因为原作者给的是图片懒得敲下来试了,看了下代码逻辑是处理pep文件,根据序列名比较同一个基因的不同转录本长度,选取最长的那个。逻辑没有问题,因为不是刚需,有需要自己再去复现,就不重复造轮子了。 WGD(全基因组复制)分析——Ka/Ks及4Dtv值计算 - 简书 (jianshu.com)","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"比较基因组学","slug":"比较基因组学","permalink":"http://www.shelven.com/categories/%E6%AF%94%E8%BE%83%E5%9F%BA%E5%9B%A0%E7%BB%84%E5%AD%A6/"}],"tags":[{"name":"MCScanX","slug":"MCScanX","permalink":"http://www.shelven.com/tags/MCScanX/"},{"name":"共线性分析","slug":"共线性分析","permalink":"http://www.shelven.com/tags/%E5%85%B1%E7%BA%BF%E6%80%A7%E5%88%86%E6%9E%90/"}]},{"title":"基因组注释(5)——预测基因筛选","slug":"基因组注释(5)——预测基因筛选","date":"2023-04-11T14:58:20.000Z","updated":"2023-04-17T11:41:58.000Z","comments":true,"path":"2023/04/11/a.html","link":"","permalink":"http://www.shelven.com/2023/04/11/a.html","excerpt":"注释得到的基因集中,可能某些基因存在被转座子插入的情况,该基因会在后续功能注释的时候被注释上,但实际在基因组中该基因可能已经被插入失活。因此在基因组的功能注释前,需要用检测转座子软件(如TransposonPSI、TEsorter等)将含有转座子的基因找出并去除。","text":"注释得到的基因集中,可能某些基因存在被转座子插入的情况,该基因会在后续功能注释的时候被注释上,但实际在基因组中该基因可能已经被插入失活。因此在基因组的功能注释前,需要用检测转座子软件(如TransposonPSI、TEsorter等)将含有转座子的基因找出并去除。 这里记录下TEsorter软件筛选预测基因的方法。 1. TEsorter安装TEsorter原本是用于调用LTR_retriever鉴别长末端重复序列反转座子(LTR-RTs),也可以用于其他类型TE的鉴别,其鉴定原理为将待测序列与数据库REXdb(整合viridiplantae_v3.0 + metazoa_v3)的TE序列进行比对。 也可以使用GyDB数据库进行比对,官网上有具体的参数用法。 zhangrengang/TEsorter: TEsorter: an accurate and fast method to classify LTR-retrotransposons in plant genomes (github.com) TEsorter提供conda安装,但是我没有安装成功,这里还是新建conda环境后手动安装各种依赖: 1234567891011121314conda create -n "TEs"conda activate TEsconda install python==3.11 # 官方要求python版本高于3,否则运行会报错conda install biopythonconda install xopenconda install hmmerconda install blastgit clone https://ghproxy.com/https://github.com/zhangrengang/TEsorter.git # 从github镜像网站下载cd TEsorterpython setup.py installTEsorter TEsorter/test/rice6.9.5.liban # 测试 2. TEsorter运行这里有一个问题,Braker预测基因有gtf和gff3两种格式的输出结果,但是两者的行数不一样。braker.aa蛋白序列和braker.codingseq基因序列的条数与gtf文件中的transcript条数一致,但是比gff3文件中的mRNA条数多(按理来说两者应该是一致的)。 后来发现Braker加入--gff3参数生成的gff3文件mRNA数量 + gtf文件的mRNA数量 = gtf文件的transcript数量,不理解为什么有这种关系,方便起见我这里处理了gtf结果文件。 TEsorter软件可以输入基因序列或者蛋白序列,以基因序列为例,简单编写脚本如下: 12345#!/bin/bash#SBATCH -n 8#SBATCH -t 7200TEsorter /public/home/wlxie/baima_pre_mydb/braker.codingseq -eval 1e-6 -p 8 大约十几分钟运行完毕。 3. 结果文件处理结果文件如下: .tsv后缀的文件中以列表形式列出了所有预测的TE类型,一个基因可能有多种类型的TE插入,因此需要处理结果文件,统计含有TE的基因,并在gtf结果文件中将该基因去除。 12345678# 筛选含有TE的基因grep -v "^#" braker.codingseq.rexdb.cls.tsv | cut -f1 | sort | uniq | cut -f1 -d "_" | sort | uniq > TE-genes.txt# 去除含有TE的基因序列grep -Fvf /public/home/wlxie/biosoft/TEsorter/baima_mydb/TE-genes.txt braker.gtf | awk '$3 ~ /gene/' > baima_gene_only.gtf# 去除含有TE的转录本序列grep -Fvf /public/home/wlxie/biosoft/TEsorter/baima_mydb/TE-genes.txt braker.gtf | awk '$3 ~ /transcript/' > baima_transcript.gtf 可以看看去除TE序列后的转录本和基因数量: 123456(base) wlxie 17:03:52 ~/baima_pre_mydb$ cat baima_transcript.gtf | wc -l23716(base) wlxie 17:04:05 ~/baima_pre_mydb$ cat baima_gene_only.gtf | wc -l20742 本想通过gffread软件根据处理后的gtf文件重新提取基因组的蛋白序列,但是运行过程中总是报错no genomic sequence available,原因暂时未知(可能是因为braker预测结果braker.gtf不是标准的gtf文件格式,同样用gffread做gtf2gff转换的时候会有部分信息丢失)。 可以直接写一个脚本处理braker.aa文件,根据前面筛选的TE-genes.txt文件,去除含有TE的蛋白序列,这里后续用到再做更新。 2023.4.13 更新gffread报错的原因找到了: 基因组文件的序列编号有空格 gtf文件缺少必要的位置信息 主要还是跑braker过程的疏忽和对gtf以及gff3数据格式的不了解。 在braker.log日志文件中有提示,基因组的fasta文件的header中包含了空格,可能会导致后续的错误,因此braker运行时自动将空格替换为了下划线“_”,这就导致了预测后的gtf文件与基因组文件无法匹配上,自然就报错no genomic sequence available。 解决方法: 手动将基因组文件中的header部分的空格用下划线“_”代替(我的基因组是59条contig,也就是手动改59个空格),并删除原fai索引文件(一定要删除,否则仍然无法找到基因序列)。 用gffread软件做gtf和gff3格式相互转换的时候,确实会损失一部分信息,但仍然会保留最基本的CDS信息。如果直接从gtf文件的第三列提取gene和transcript信息保存成新的gtf文件,这个新的gtf文件是无法用gffread定位和提取蛋白序列的。 这一点在GTF官方文档对第三列的<feature>解释中有提到: <feature>The following feature types are required: “CDS”, “start_codon”, “stop_codon”. The features “5UTR”, “3UTR”, “inter”, “inter_CNS”, “intron_CNS” and “exon” are optional. All other features will be ignored. The types must have the correct capitalization shown here. 也就是说我如果从第三列只提取gene或者transcript,这些feature是会被忽略的,使用gffread提取蛋白序列会提示这是一个非法的GTF文件。 因此,正确的筛选方式和提取蛋白的方式应该为: 12345678# 筛选含有TE的基因grep -v "^#" braker.codingseq.rexdb.cls.tsv | cut -f1 | sort | uniq | cut -f1 -d "_" | sort | uniq > TE-genes.txt# 去除含有TE的序列grep -Fvf /public/home/wlxie/biosoft/TEsorter/baima_mydb/TE-genes.txt braker.gtf > baima_rmTE.gtf# 从基因组重新提取去除了TE的蛋白序列gffread baima_rmTE.gtf -g /public/home/wlxie/biosoft/db_data/baima/RepeatMasker_soft/genome.nextpolish.fasta.masked -y pep_rmTE.fa","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"TEsorter","slug":"TEsorter","permalink":"http://www.shelven.com/tags/TEsorter/"}]},{"title":"基因组注释(4)——基因预测","slug":"基因组注释(4)——基因预测","date":"2023-04-03T14:09:26.000Z","updated":"2023-04-08T02:38:55.000Z","comments":true,"path":"2023/04/03/a.html","link":"","permalink":"http://www.shelven.com/2023/04/03/a.html","excerpt":"在对基因组重复序列和ncRNA进行注释后,接下来是基因预测和功能注释,这也是寻找功能基因的基础和前提。这里主要记录下怎么用Braker3进行基因组的基因预测(也就是结构注释)。","text":"在对基因组重复序列和ncRNA进行注释后,接下来是基因预测和功能注释,这也是寻找功能基因的基础和前提。这里主要记录下怎么用Braker3进行基因组的基因预测(也就是结构注释)。 基因预测的方法主要有三种: 基于隐马尔可夫模型的自训练和迭代,获得从头预测的基因结构模型(软件Augustus、GeneMark-ES等) 基于已发表的近缘物种基因序列、蛋白序列的同源预测(软件DIAMOND、GeMoMa等) 基于本物种的RNA-Seq转录组数据,比对基因组内含子结构模型和基因侧翼序列信息(软件Hisat2、STAR等) 一般的流程是将以上三种基因预测结果通过软件EvidenceModeler(EVM)进行整合,最终得到预测结果gff文件。网上对于上面的流程有很多教程,针对不同软件有不同的设置,比如知乎的这篇文章使用AUGSTUS + Geneid + GeneMark + GeMoMa + GenomeThreader + Exonerate 进行基因结构预测 - 知乎 (zhihu.com) 为了简化流程,现在也有越来越多的基因预测pipeline工具得以开发,比较有名的就是Braker和Maker,感兴趣的话可以做两者预测结果的比较,我这里就用发表时间比较近的Braker3为例。 Braker本质上是一个结合了多种基因组注释工具的perl程序,其核心为braker.pl文件。 1. 安装Braker3目前为止(2023年4月3日)Braker的最新版本为3.0.2,conda上能搜到的最新版本只有2.1.6,因此不建议用conda安装,尤其是最新的版本Braker可以直接使用RNA-seq和蛋白数据,整合GeneMark-ETP和AUGUSTUS训练和预测基因,对于预测结果有较高的支持度。 因为整个pipeline包含了十几个注释用的软件,用到的perl模块也非常多(数了一下配置环境需要安装20个perl模块),还是推荐用给官方给的**container**。 1.1 申请和下载GeneMark-ETP密钥在Braker3中使用RNA-seq数据和蛋白数据预测基因,都要用到GeneMark-ETP这个软件。但是这个软件不能直接用,需要到GeneMark网站申请和下载对应的密钥文件放在集群用户的家目录中。 申请完成之后获得名称为gm_key_64.gz的密钥文件,解压之后命名为.gm_key(注意点号)并上传到集群用户的家目录下即可。 12gunzip gm_key_64.gzmv gm_key_64 .gm_key 1.2 创建Braker3镜像这一步在dockerhub网站的Braker3仓库中有详细说明,我这里选择创建singularity镜像: 1singularity build braker3.sif docker://teambraker/braker3:latest 得到的braker3.sif就是Braker3的singularity image 创建braker3镜像文件的环境变量: 1export BRAKER_SIF=/your/path/to/braker3.sif 可以复制三个示例脚本到当前目录: 123singularity exec -B $PWD:$PWD braker3.sif cp /opt/BRAKER/example/singularity-tests/test1.sh .singularity exec -B $PWD:$PWD braker3.sif cp /opt/BRAKER/example/singularity-tests/test2.sh .singularity exec -B $PWD:$PWD braker3.sif cp /opt/BRAKER/example/singularity-tests/test3.sh . 在本地申请计算资源并跑一下三个示例脚本: 12345salloc -n 50 # 申请50个核跑test.sh,注意不要在登录节点直接运行计算程序bash test1.sh # tests BRAKER1bash test2.sh # tests BRAKER2bash test3.sh # tests BRAKER3exit # 退出并释放计算资源 2. 运行Braker3官方提供了4种BRAKER pipeline 模式: RNA-Seq数据跑BRAKER 蛋白数据跑BRAKER 整合RNA-Seq数据以及蛋白数据跑BRAKER 整合短读长与长读长的RNA-Seq数据以及蛋白数据跑BRAKER 4种pipeline模式在调用软件的方法上有区别,根据自己手上有的数据选择用哪种,我这里选择第三种。 上图是整合RNA-Seq数据和蛋白数据跑Braker的流程图,需要注意基因组文件genome.fa在输入前需要需要进行softmasking(重复序列屏蔽为小写字母),官方建议不要用hardmasking(重复序列屏蔽为N),hardmasking后预测的基因数量会偏少,因为重复序列中可能也有功能基因的部分信息,屏蔽为N后就无法检测到了。 对RNA-Seq数据的处理,首先是通过SRA tookit将SRA ID对应的fastq数据下载下来(如果本来就是fastq格式就不需要这一步),用Hisat2比对到softmasking后的参考基因组并生成bam文件,再用stringtie进行转录本组装。 GeneMark-ETP以组装后的转录本和同源蛋白数据库作为输入数据进行训练和预测,之后再用AUGUSTUS软件结合上一步的预测结果进行训练和预测,最后用TSEBRA对预测的基因集进行整合,得到最终的gtf结果文件。 barker.sh脚本可以如下编写: 123456789101112#!/bin/bash#SBATCH -n 48#SBATCH -t 7200wd=baima_preif [ -d $wd ]; then rm -r $wdfisingularity exec -B ${PWD}:${PWD} ${BRAKER_SIF} braker.pl --genome=/public/home/wlxie/biosoft/db_data/baima/RepeatMasker_soft/genome.nextpolish.fasta.masked --prot_seq=/public/home/wlxie/busco_soft/busco/test_data/eukaryota/busco_downloads/lineages/eudicots_odb10/refseq_db.faa --softmasking --threads 48 --workingdir=${wd} --rnaseq_sets_dirs=/public/home/wlxie/RNAseq/BYT2022020901/rnaseq/baima --rnaseq_sets_ids=4-216031965_raw 几个参数的解释: genome softmasking后的基因组文件位置 prot_seq 同源蛋白库的文件位置 –softmasking mask的方式 –threads 跑程序用的核数 –workingdir 工作目录位置 rnaseq_sets_dirs RNA-Seq数据所在目录 –rnaseq_sets_ids 双端测序数据文件前缀(比如我这里是4-216031965_raw_1.fq和4-216031965_raw_2.fq) 说明一下同源蛋白来源于前面做BUSCO评估的真双子叶植物单拷贝直系同源库,怎么来的详情可见这篇博客(同源蛋白库建议用官方推荐的OrthoDB或者找几个模式植物的蛋白数据合并,见博客最下方的更新) 前面说过塔大集群的计算节点没有安装singularity,所以在运行该容器的时候要在申请核在本地跑程序,并且用screen维持当前会话: 1234567screen -S singularity # 创建singularity会话salloc -n 48 -t 7200 # 申请计算资源bash barker.shscreen -r singularity # 进入singularity会话exit # 退出会话exit # 运行结束释放计算资源 正常跑完花费了9个小时时间(200Mbp大小的基因组),如果中途不幸出bug,braker支持有限度的断点重新运行,主要分为以下三个阶段: 只要有中间文件存在,就可以在这三个阶段继续加入其他参数,跳过已经运行的阶段继续运行,详情可以看官方文档https://github.com/Gaius-Augustus/BRAKER#starting-braker-on-the-basis-of-previously-existing-braker-runs 3. 结果文件可以在前面给定的工作目录中看到如下结果文件: braker.gtf——Braker预测的基因集,包括了各种不同的基因结构预测结果 braker.codingseq——fasta格式的编码序列基因集(基因序列) braker.aa——fasta格式的蛋白序列基因集(蛋白序列) braker.gff3——需要–gff3参数指定,这里我没有,就是基因集的gff3格式 Augustus/*——AUGUSTUS预测的基因集(包括gtf文件、基因序列和蛋白序列) GeneMark-ETP/*——GeneMark-ETP预测的基因集以及其他中间文件 hintsfile.gff——从RNA-Seq数据和蛋白库数据中提取的外部证据数据 可以通过awk命令查看gft文件的第三列,查看预测的编码蛋白基因数量和转录本数量: 12345$ awk '$3=="gene"' braker.gtf | wc -l17869$ awk '$3=="transcript"' braker.gtf | wc -l20832 后续就可以对这些预测的基因做质量评估,然后比对各数据库做功能注释。 2023.4.7 更新1. OrthoDB蛋白数据库下载官方推荐使用OrthoDB数据库作为同源蛋白来源。 123456789101112# 真菌Fungi: https://v100.orthodb.org/download/odb10_fungi_fasta.tar.gz# 后生动物Metazoa: https://v100.orthodb.org/download/odb10_metazoa_fasta.tar.gz # 节肢动物 - Arthropoda: https://v100.orthodb.org/download/odb10_arthropoda_fasta.tar.gz # 脊椎动物 - Vertebrata: https://v100.orthodb.org/download/odb10_vertebrata_fasta.tar.gz# 单细胞生物Protozoa: https://v100.orthodb.org/download/odb10_protozoa_fasta.tar.gz# 绿色植物Viridiplantae: https://v100.orthodb.org/download/odb10_plants_fasta.tar.gz 我这里要分析的物种是植物,所以下载最后一个绿色植物蛋白库: 123nohup wget https://v100.orthodb.org/download/odb10_plants_fasta.tar.gz &tar zxvf odb10_plants_fasta.tar.gzcat plants/Rawdata/* > plant_proteins.fasta 绿色植物蛋白库约780Mb大小,建议用wget下载,如果windows下载再ftp拖拽上传到集群可能会损坏文件(并且没有md5效验码没办法确认是否真的损坏)。 解压并将所有数据合并到一个文件plant_proteins.fasta中,截至目前2023年4月7日为止,这个数据库共有3510742条蛋白序列,总文件大小为1.4Gb,比原来我比对的蛋白库大了1500倍。而蛋白比对是一个很缓慢的过程,因此这一步预测基因的时间将会很长,可以根据自己要做的物种确定用哪些注释比较完善的模式生物的蛋白库。 2. 自建蛋白数据库在NCBI网站上直接找一些组装注释结果较好的模式生物和近缘物种蛋白: 12345678910111213# 拟南芥(TAIR10.1)wget https://ftp.ncbi.nlm.nih.gov/genomes/all/GCF/000/001/735/GCF_000001735.4_TAIR10.1/GCF_000001735.4_TAIR10.1_protein.faa.gz# 栽培烟草(Ntab-TN90)wget https://ftp.ncbi.nlm.nih.gov/genomes/all/GCF/000/715/135/GCF_000715135.1_Ntab-TN90/GCF_000715135.1_Ntab-TN90_protein.faa.gz# 水稻(IRGSP-1.0)wget https://ftp.ncbi.nlm.nih.gov/genomes/all/GCF/001/433/935/GCF_001433935.1_IRGSP-1.0/GCF_001433935.1_IRGSP-1.0_protein.faa.gz# 近缘物种coffea arabicawget https://ftp.ncbi.nlm.nih.gov/genomes/all/GCF/003/713/225/GCF_003713225.1_Cara_1.0/GCF_003713225.1_Cara_1.0_protein.faa.gz# 近缘物种coffea canephorawget https://ftp.ncbi.nlm.nih.gov/genomes/all/GCA/900/059/795/GCA_900059795.1_AUK_PRJEB4211_v1/GCA_900059795.1_AUK_PRJEB4211_v1_protein.faa.gzgunzip *.gzcat *.faa > mydb_proteins.fasta 顺便记录一下近缘物种的同源蛋白是如何找到的: plant Biology - Usadel lab (plabipd.de)这个网站记录了多种已发表的植物基因组文章和数据,点击cladogram view可以直观地看到已测过基因组的植物学名和树状图,比如我要找的物种是夹竹桃科(Apocynaceae),直接ctrl + F 就可以定位到夹竹桃科所处的进化节点。 然后用Apocynaceae祖先节点和子节点的已发表基因组的植物学名,一个一个去搜NCBI网站的Genome库,有protein序列的就可以直接下载。 两种同源蛋白建库方式预测的基因数量和花费的时间: OrthoDB Plant数据库 自建蛋白数据库 花费时间 13 h 12.5 h 预测基因数 23746 23953 用自建蛋白数据库跑braker预测的基因数更多,且花费时间更短。后续以该预测结果继续分析。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"Barker3","slug":"Barker3","permalink":"http://www.shelven.com/tags/Barker3/"}]},{"title":"Apptainer/Singularity使用方法记录","slug":"Apptainer-Singularity使用方法记录","date":"2023-03-28T16:05:34.000Z","updated":"2023-03-28T16:21:56.000Z","comments":true,"path":"2023/03/29/a.html","link":"","permalink":"http://www.shelven.com/2023/03/29/a.html","excerpt":"在做生信分析的时候,难免会遇到一个pipeline上的软件存在冲突的情况,一般的解决方法是创建不同的conda环境,然后分别在不同的环境下跑不同的软件。这种操作可以解决环境冲突的问题但不适合写流程化的脚本,同时又非常占用空间。有的软件整合了pipeline流程的所有软件,按照顺序进行调用,这种软件虽然可以节省时间实现自动化分析,但是环境依赖的问题更加复杂,因此这一类的软件也往往提供容器来方便人们在一个封闭的环境中使用。","text":"在做生信分析的时候,难免会遇到一个pipeline上的软件存在冲突的情况,一般的解决方法是创建不同的conda环境,然后分别在不同的环境下跑不同的软件。这种操作可以解决环境冲突的问题但不适合写流程化的脚本,同时又非常占用空间。有的软件整合了pipeline流程的所有软件,按照顺序进行调用,这种软件虽然可以节省时间实现自动化分析,但是环境依赖的问题更加复杂,因此这一类的软件也往往提供容器来方便人们在一个封闭的环境中使用。 这篇博客主要讲一讲关于容器的一些基本常识,以及记录下学校集群中singularity的使用方法。 1. 容器(Container)前面说到为了规避软件与现有环境依赖冲突,我们往往会把一个pipeline的软件封装到一个容器中。容器是一种在Linux系统上广泛采用的应用封装技术,它将可执行程序与依赖库打包成一个镜像文件,启动时与宿主节点共享操作系统内核。 镜像(Image):可执行的独立软件包,用于保存环境 实例(Instance):基于镜像启动的运行实例,运行实际任务,不同实例之间互相隔离 由于这个镜像文件自带了可执行文件和依赖库,因此不需要用到宿主机的依赖库,也就从源头上避免了环境冲突的情况。听起来这种实现方式类似于虚拟化技术,但还是有一些区别的:前面说过容器启动时与宿主机共享操作系统内核,没有运行独立的操作系统任务,在资源的占用上明显低于虚拟机。虚拟化可以认为它更全面和彻底一些,每个虚拟机从宿主机的物理框架中分割出来,有自己的一整套操作系统,会运行各种独立的操作系统任务,即使没有运行程序也会消耗内存和系统资源。 上图来自Microsoft Azure容器与虚拟机的比较。总结来说,在安全性和隔离性上虚拟机优于容器,在资源占用、可移植性和运行速度上,容器优于虚拟机。 现有的容器软件比较多,Docker(5.8k star)是代表性的软件之一,其他开源容器化工具还有Podman(17.4k star,无守护进程的容器技术,无需root权限)、LXD(3.8k star,可运行多个进程)、Apptainer(以前叫singularity,2.4k star,无需root权限)、Containerd(13.5k star)和RunC(10.1k star)等。 这些开源的容器化技术软件各有其特点,详情可以点各自的链接了解,这里就不过多介绍了。主要说下塔大集群部署的singularity(现在已改名为Apptainer,很多人不知道改名了,两个名字就放一块儿说)的使用方法。 2. Apptainer/Singularity首先还是要说明以一下什么时候选择用容器,并不是说每一个软件都要用容器封装——反而这样是对系统资源的浪费。一般是在需要批量部署环境、或者快速部署一个pipeline环境的时候选择用容器。在HPC上进行大规模计算的时候,一般考虑安全性不会用Docker(需要root权限),Apptainer/Singularity这种无需root权限的容器工具是最好的选择。 需要说明一下,两年前Singularity改名为Apptainer,并且整个项目已经转移成为了Linux Foundation的一部分。Apptainer用法和Singularity几乎一模一样,可以参考https://apptainer.org/docs/user/latest/quick_start.htmlhttps://docs.sylabs.io/guides/latest/user-guide/quick_start.html#两个官方手册。因为塔大超算预装了singularity,以下统一用Singularity命令来讲解。 Singularity的镜像文件以.sif为后缀(Singularity Image File, SIF),且该文件是只读的,这和Docker镜像文件有本质上的区别。 2.1 使用镜像库获取镜像文件Singularity image文件是基于Docker image创建的: 1234567singularity -d build braker3.sif docker://teambraker/braker3:latest# 可以获取的镜像文件库(云平台)有以下几种# Sylabs cloud library library://# Docker docker://# Shub shub://# OCI registry oras://# 创建之后的文件名为braker3.sif build/pull 这两个命令都可以拉取镜像文件并创建为singularity的sif文件 创建的image路径、名字都可以在-d参数后根据需要自己改 当一个软件提供docker镜像,我们就可以通过上面的方法下载并创建一个singularity镜像,需要注意这个镜像文件是只读的。 2.2 创建自定义镜像文件这里顺带提一下如何制作自定义的SIF镜像文件: 12345678singularity -d build --sandbox ubuntu/ docker://ubuntu# 以沙盒的形式创建一个空的操作系统,放在ubuntu这个文件夹中singularity shell --writable ubuntu# --writable或者-w以可修改模式进入沙盒。也可以不用这种方法进入,直接进入对应的文件位置修改即可singularity build name.sif ubuntu# 创建sif镜像文件 进入沙盒,就可以和正常的linux操作系统一样进行安装软件,最后build制作成名为name.sif的singularity镜像,和2.1从镜像库拉取创建的镜像后续是一样的用法。 第二步以可修改模式进入沙盒时可能会有如下提示: 12WARNING: By using --writable, Singularity can't create /public destination automatically without overlay or underlayFATAL: container creation failed: mount /var/singularity/mnt/session/public->/public error: while mounting /var/singularity/mnt/session/public: destination /public doesn't exist in container 无法自动创建public这个文件夹,并且进入/var/singularity/mnt/session/这个文件夹下你会发现是空的,此时需要手动在你创建ubuntu的文件夹中创建public文件夹 mkdir public,提示缺少其他文件也是一样的处理方法,缺啥创建啥,就可以正常进入了。 需要注意,塔大集群无法制作sif镜像文件!!!会在最后一步build的时候提示permission denied,因此,创建自定义镜像文件要在自己的计算机上(拥有root权限),制作完成之后的sif镜像文件可以上传到集群中再运行。 2.3 运行镜像文件singularity主要有两种运行方式,一种是执行镜像文件中的命令 singularity exec;一种是进入交互模式singularity shell 12345singularity exec name.sif test/test.pl # 运行name.sif镜像文件中test文件夹下的test.pl程序singularity shell name.sif# 以交互模式进入name.sif镜像文件 需要注意,运行镜像文件后,singularity会自动挂载当前目录$PWD、用户家目录$HOME和宿主机的/tmp目录,对这些目录的文件进行修改会影响到原文件。对于一般的程序来说已经足够了,如果需要访问宿主机的其他目录,需要用--bind将宿主机目录映射到容器内。 12345singularity exec --bind /pub/software:/mnt name.sif python test.py# --bind挂载宿主机的文件夹,冒号前为宿主机的路径,冒号后为容器中的路径。内容挂载到/mnt中singularity exec --bind /pub/software name.sif python test.py# 不写挂载点,则与宿主机的目录一致。内容挂载到/pub/software中 2.4 在集群中提交singularity作业一般来说登录节点预装了singularity,计算节点也会装singularity才对,但是无论我直接用singularity命令还是指定singularity命令的绝对路径,在sbatch提交作业后都是提示该命令不存在。很疑惑,又去查了一些资料,发现有的平台需要在作业里module load singularity之后才可以加载,但是塔大集群用这个指令仍然行不通,只有本地(登录节点)才可以使用该命令。 百思不得其解,询问集群管理员暂时没有答复,后续有新消息会更新。这里说一下我的解决方法: 既然可以在本地运行singularity,那就可以用salloc申请计算资源,在本地跑程序。但是我又不可能一直坐在电脑跟前,因此需要用到screen这个工具维持当前会话。 1234567891011screen -S test# 创建名为test的session对象salloc -n 50 -t 7200# 申请50核的计算资源export BRAKER_SIF=\\$PWD/braker3.sif# 给host添加环境变量(非必须,打个比方)singularity esec braker3.sif braker.pl # 运行容器中的程序 运行之后就可以双手离开键盘,关上电脑,等待第二天程序运行结束了。 再次打开session的时候只需要运行如下命令: 12345678screen -r test# 再次打开sessionexit# 退出sessionexit# 释放计算资源 注意需要两次exit之后才会回到原来的登录节点。 singularity还有很多其他参数,对我而言暂时用不到,需要用的时候再更新。 可以参考武大超算中心的文档运行 Singularity · GitBook (whu.edu.cn)和北鲲云的介绍文档https://www.cloudam.cn/helpce/docs/2030/about2/。(可恶,我们学校集群什么时候能出一个详细的文档啊。好想吐槽,slurm调度系统和各种软件都要自己学,功能还不一定全,也不知道到底预装了哪些东西,以后要是我来管理集群一定会做详细的文档介绍所有花里胡哨的功能,而不是藏着不告诉用户) 2023.3.29更新破案了,计算节点确实没装singularity(噎住.jpg)","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"容器","slug":"容器","permalink":"http://www.shelven.com/tags/%E5%AE%B9%E5%99%A8/"},{"name":"singularity","slug":"singularity","permalink":"http://www.shelven.com/tags/singularity/"}]},{"title":"基因组注释(3)——ncRNA注释","slug":"基因组注释(3)——ncRNA注释","date":"2023-03-21T16:29:28.000Z","updated":"2023-03-21T16:31:39.000Z","comments":true,"path":"2023/03/22/a.html","link":"","permalink":"http://www.shelven.com/2023/03/22/a.html","excerpt":"非编码RNA(non-coding RNA,ncRNA)指不编码蛋白质的RNA,包括rRNA、tRNA、snRNA、snoRNA 和 microRNA 等多种已知功能的 RNA,和未知功能的RNA。tRNA预测可以使用经典的tRNAscan-SE,其他类型的RNA都可以用Infernal+Rfam数据库方式预测。","text":"非编码RNA(non-coding RNA,ncRNA)指不编码蛋白质的RNA,包括rRNA、tRNA、snRNA、snoRNA 和 microRNA 等多种已知功能的 RNA,和未知功能的RNA。tRNA预测可以使用经典的tRNAscan-SE,其他类型的RNA都可以用Infernal+Rfam数据库方式预测。 1. tRNAscan-SEtRNAscan-SE的安装需要依赖Infernal软件,因此可以用conda直接安装tRNAscan-SE顺带解决依赖问题: 1conda install -c bioconda trnascan-se 写一个脚本运行tRNAscan-SE: 12345#!/bin/bash#SBATCH -n 50#SBATCH -t 7200tRNAscan-SE --thread 50 -E -I -m tRNA_luobuma.stats -o tRNA_luobuma.out -f tRNA_luobuma.ss /public/home/wlxie/NextPolish/luobuma_rundir/genome.nextpolish.fasta 参数解释: -E 搜寻真核生物tRNA -I 使用Infernal软件进行搜索 -m 保存结果统计文件 -o 输出tRNA预测结果 -f 保存tRNA的二级结构 也可以直接使用-j参数保存gff3格式,-b参数保存bed格式,详情可以见tRNAscan-SE -h 生成的二级结构结果文件如下: str一行记录的二级结构信息,每个<>是互相配对的,代表在二级结构中这两个碱基连在一起。可以通过其他软件(如VARNA)绘制成图。 输出的out文件也推荐转成gff文件方便在基因组上可视化,因为我这里只是粗略统计一下tRNA数量,所以只看stat文件就可以了: 可以看到第一轮预测出526个tRNA,通过Infernal验证的有479个。 2. Infernal + RfamInfernal(INFERence of RNA ALignment)是Sanger实验室开发的ncRNA预测软件,他们建立了1600多个RNA家族,每个家族建立了一致性二级结构和协方差模型,也就是Rfam数据库。总体的注释思路是基因组与 Rfam数据库进行比对,Rfam是一个RNA分类数据库,其比对方法是调用软件Infernal中的程序cmscan,将提交的序列在Rfam.cm数据库中进行检索,从而得到其比对的结果。 cmscan(search sequence(s) against a covariance model database, 针对协方差模型数据库的序列搜索),主要参考官方手册Userguide.pdf (eddylab.org)中的Searching the Rfam CM database with a query sequence步骤。 前面已经安装了Infernal,这里需要再下载一个Rfam数据库。 1234567# 下载Rfam数据库,注意两个文件版本必须一致wget ftp://ftp.ebi.ac.uk/pub/databases/Rfam/CURRENT/Rfam.cm.gzwget ftp://ftp.ebi.ac.uk/pub/databases/Rfam/CURRENT/Rfam.clanin# 解压建库gunzip Rfam.cm.gzcmpress Rfam.cm 官方手册这里使用默认参数运行cmscan,稍微注意下-Z,以下是官方对-Z参数的定义: -Z Calculate E-values as if the search space size was megabases (Mb). Without the use of this option, the search space size changes for each query sequence, it is defined as the length of the current query sequence times 2 (because both strands of the sequence will be searched) times the number of CMs in . Z值代表搜索数据库的大小(database size),是和E-Values计算相关的,在默认情况下,每一个query sequence的-Z参数值是不同的,等于query sequence本身的碱基数*2*CM数据库中模型的数量,只有E-Values小于10的hits会被报道。 在作者的原文中,可以找到这么一句话: To manually set the database size used in the E-value calculation to megabases when running cmsearch or cmscan on the command line, use the -Z option. It makes sense to do this if, for example, a large sequence file has been split up into many smaller files, and searches have been performed in parallel on a compute cluster, with the results combined. In that scenario, if is set as the total number of models used times the total number of nucleotides in all sequence files times two (for both strands), then the combined results should have appropriate E-values. That is, the expectation is that in the collection of all hits between all sequences and models there will be about 1 hit with an E-value of 1 or below by chance (not due to homology), about 10 with an E-value of 10 or below by chance, etc. Non-coding RNA analysis using the Rfam database - PMC (nih.gov) 也就是说一个大的序列文件被分割成数个文件,并在一个集群上并行搜索,最终将结果文件整合的时候,设置Z值为使用的CM模型数量*所有序列文件的核苷酸总数*2,合并的结果会有一个适当的E-value值。 前面建库的时候可以看到CM数据库中模型的数量: CM模型的数量不可以直接用cat Rfam.cm | grep "ACC" | wc -l这种方式查询,你会发现这种只搜索ACC或者NAME字段查找到的数量是实际数量的两倍(包括了HMM filter)。 Z值= 基因组核苷酸数*2*数据库中模型数量/1000000 1esl-seqstat my-genome.fa # HMMER插件,统计基因组大小,计算Z值用 因此这里的Z值计算如下:$$Z = 22308888634108/1000000=1896982.90$$但是很神奇的是,在Rfam的帮助文档中给了一个对古菌Methanobrevibacter ruminantium注释ncRNA的例子,其中也用了Rfam数据库的,计算Z值时没有乘以CM模型的数量,我也有点疑惑,以下是链接地址。 Genome annotation — Rfam Help documentation For the purposes of Infernal, the total database size is the number of nucleotides that will be searched, in units of megabases (Mb, millions of nucleotides). So, it is the total number of nucleotides in all sequences that make up the genome, multiplied by two (because both strands will be searched), and divided by 1,000,000 (to convert to millions of nucleotides). 就 Infernal 而言,数据库总大小是将要搜索的核苷酸数量,以兆碱基(Mb,百万核苷酸)为单位。因此,它是构成基因组的所有序列中的核苷酸总数乘以2(因为将搜索两条链),然后除以 1,000,000(转换为Mb)。 两种方法计算Z值相差4000多倍,我无法断定哪种是正确的,后续有更深的理解再更新(也许是版本问题?)。 这里我按照文章作者给的默认参数对基因组进行注释(也就是没有指定Z值,每条序列的Z值是变动的)。 运行脚本如下: 12345#!/bin/bash#SBATCH -n 50#SBATCH -t 7200cmscan --cut_ga --rfam --nohmmonly --tblout luobuma.tblout --fmt 2 -o luobuma.out --clanin Rfam.clanin Rfam.cm /public/home/wlxie/NextPolish/luobuma_rundir/genome.nextpolish.fasta 参数解释: –cut_ga 指定Rfam GA阈值,决定哪些hits可以报告。有多种标准,可以见Glossary — Rfam Help documentation –fram 以快速模式运行 –nohmmonly 决定所有模型都是CM模型(非HMM模型) –tblout 表格形式输出结果 –fmt 2 输出格式2,包括overlapping hit的注释 -o 标准输出文件 –clanin Rfam.clanin文件的位置,该文件记录哪些模型属于同一家族 最终获得luobuma.out的标准输出文件和整理成表格的luobuma.tblout文件,这里整理一下表格文件: 27列对应预测ncRNA类型和信息;前后都有#键注释的行,除此之外每一行是预测的ncRNA具体内容 需要注意olp这一列,在infernal 1.1.4版本这一列有以下四个值: * indicates this hit does not overlap with any other reported hits 这条序列与其他已报道的序列之间无重叠区域(保留) ˆ indicates that this hit does overlap with at least one other hit, but none of the hits that overlap with it have a lower E-value (occur above it in the hit list) 这条序列与至少一条已报道的序列之间有重叠区域,但是这条序列的E-value最低(保留) $ indicates that this hit does overlap with at least one other hit that does have a lower E-value (occurs above it in the hit list) but none of those higher scoring hits have ˆ in this column 这条序列与至少一条已报道序列之间有重叠区域,且其他序列E-value更低但不是最低的(过滤,1.1.2版本的infernal中没有) = indicates that this hit does overlap with at least one other hit that has a lower E-value (occurs above it in the hit list) and does itself have a ˆ in this column 这条序列与至少一条已报道序列之间有重叠区域,且其他序列的E-value值更低且是最低的(过滤) 我们只关注预测结果中准确度最高的ncRNA,记录种类和长度,因此可以写个python脚本处理并统计数据。 因为这里输出的列表没有具体给出分类,因此对数据处理前要去Rfam官网的Entry type search栏下找到每个accession号对应的ncRNA类型Rfam: Search Rfam: 我这里只关心上面红框中的ncRNA(根据情况自己选择),勾选之后点击submit: 拉到最底下看到官网提供未格式化的列表,点击show,将显示的列表复制粘贴到一个新建的accession.txt文档,接下来就是对tblout文件和accession.txt文件处理: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970'''Infernal预测ncRNA结果文件统计脚本2023.3.22'''# 结果文件数据过滤,获取每条预测ncRNA的accession号和长度loci_length = []accession = []with open('./luobuma.tblout', 'r') as input: for i in input.readlines(): if i.find('#') != -1 or i.find('=') != -1 or i.find('$') != -1: continue else: lst = i.strip().split() if len(lst) < 1: continue length = abs(int(lst[10]) - int(lst[9])) loci_length.append(length) accession.append(lst[2])len_sum = 0for i in loci_length: len_sum += i# 处理accession.txt,提取accession号和ncRNA类型的关系accession_num = []dicts = {}with open('./accession.txt', 'r') as ac: for i in ac.readlines(): m = i.strip().split('\\t') # 以制表符分割 accession_num.append(m[0]) nc_type = m[2].split(';')[1].strip() # 获取第三列第二个分号处的ncRNA类型 if m[0] not in dicts: dicts[m[0]] = nc_type# 统计结果文件accession号对应的ncRNA类型数量mi = s = sn = lnc = t = r = other = 0mi_len = s_len = sn_len = lnc_len = t_len= r_len = other_len =0for i in range(len(accession)): item = accession[i] if item in dicts: if dicts[item] == 'miRNA': mi += 1 mi_len += int(loci_length[i]) elif dicts[item] == 'sRNA': s += 1 s_len += int(loci_length[i]) elif dicts[item] == 'snRNA': sn += 1 sn_len += int(loci_length[i]) elif dicts[item] == 'lncRNA': lnc += 1 lnc_len += int(loci_length[i]) elif dicts[item] == 'tRNA': t += 1 t_len += int(loci_length[i]) else: r += 1 r_len += int(loci_length[i]) else: other += 1 other_len += int(loci_length[i])outputlst = [('miRNA', mi, mi_len), ('sRNA', s, s_len), ('snRNA', sn, sn_len), ('lncRNA', lnc, lnc_len), ('tRNA', t, t_len), ('rRNA', r, r_len), ('others', other, other_len), ('total', len(accession), len_sum)]# 输出结果with open('./res.xls', 'w') as output: output.write('Type\\tCopy Number\\tTotal length(bp)\\n') for i in outputlst: type = str(i[0]) number = str(i[1]) length = str(i[2]) output.write(type + '\\t' + number + '\\t' + length + '\\n') 我对python掌握的不好,统计的地方可以写个循环的,怕自己绕不清楚这里就用最笨的方法….. 结果统计如下: tRNA这里预测470,与上面的tRNAscan-SE预测的479个基本没有差别。 顺带说一下,Rfam官网上也说了这种方法可以统计所有类型的RNA,如果针对不同ncRNA有特殊需求的话,可以用不同软件进行分析(但是RNAMMER现在似乎已经用不了了)。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"tRNAscan-SE","slug":"tRNAscan-SE","permalink":"http://www.shelven.com/tags/tRNAscan-SE/"},{"name":"Rfam/Infernal","slug":"Rfam-Infernal","permalink":"http://www.shelven.com/tags/Rfam-Infernal/"}]},{"title":"SSL证书申请和部署","slug":"SSL证书申请和部署","date":"2023-03-17T10:00:39.000Z","updated":"2023-04-13T08:41:09.000Z","comments":true,"path":"2023/03/17/a.html","link":"","permalink":"http://www.shelven.com/2023/03/17/a.html","excerpt":"不知不觉这个小破站运行也快要一年整了,一年前网站刚开放,我天天修bug到凌晨两三点的情景还历历在目……主要还是自己对网站搭建框架不熟悉,看不懂代码整不清楚linux操作(虽然现在也没好到哪儿去)。一年过去了通过自学确实学习了很多计算机方面的知识,有空再做个总结吧~ 一年前想写如何部署ssl证书的,如今一年快到了正好要续上ssl证书,这篇博客算是补档吧~记录下自己的操作","text":"不知不觉这个小破站运行也快要一年整了,一年前网站刚开放,我天天修bug到凌晨两三点的情景还历历在目……主要还是自己对网站搭建框架不熟悉,看不懂代码整不清楚linux操作(虽然现在也没好到哪儿去)。一年过去了通过自学确实学习了很多计算机方面的知识,有空再做个总结吧~ 一年前想写如何部署ssl证书的,如今一年快到了正好要续上ssl证书,这篇博客算是补档吧~记录下自己的操作 1. SSL部署的意义前面在http原理部分说过,HTTPS的安全基础是ssl,部署ssl之后有以下优点: 建立数据信息安全通道,保障信息安全 有https协议的网站更容易被google、baidu收录 用户浏览https协议的网站地址有锁头标志,不会显示信息安全提醒页面 2. 申请SSL证书我是在腾讯云上购买的轻量云服务器,在哪个服务器供应商买的服务器就到对应的平台,搜索SSL证书,点击申请免费证书 选择第一个免费版即可,填写你要绑定的域名,验证方法选择手动DNS验证,提交申请 这一步需要到对应的域名供应商那里进行DNS解析 我是在阿里云买的域名,因此在阿里云控制台找到你要绑定的域名,添加记录,输入上面图红框里的对应信息 回到腾讯云,点击验证域名即可,后续根据自己的服务器类型(我是Apache 服务器)选择对应的证书。第一次申请的话可能会让你完善身份信息。需要注意,如果域名被托管到其他平台,需要到对应的托管平台进行DNS解析,否则会查询不到解析记录。 3. 部署SSL证书申请成功后,进入SSL证书管理平台,点击已签发,就可以看到刚刚申请的SSL证书了,首先把证书下载到本地 解压可以看到如下四个文件,.crt后缀的是证书链和证书文件,.csr后缀的是提供给CA的文件,.key后缀是私钥文件 再次强调一下Apache服务器、Nginx服务器等等的部署目录是不同的,我这里是apache服务器,需要将上面的四个文件上传到服务器/etc/httpd/ssl/目录下。 如果之前部署过ssl证书,到这一步以后直接service network restart重启http服务就行了。 如果是第一次安装,进入/etc/httpd/conf目录,修改 httpd.conf 配置文件: 1Include conf.modules.d/*.conf # 第56行确保该命令未被注释,用于加载SSL的配置目录 进入/etc/httpd/conf.modules.d目录,修改00-ssl.conf 配置文件: 1LoadModule ssl_module modules/mod_ssl.so # 第1行确保该命令未被注释,用于加载SSL模块 进入/etc/httpd/conf.d目录,修改ssl.conf配置文件: 123456DocumentRoot "/var/www/html" # 配置虚拟主机的位置,路径可以改,建议还是用默认的ServerName xxxxx.com # 填写证书网站名称SSLEngine on # 确保SSL功能打开SSLCertificateFile /etc/httpd/ssl/xxxxx.com.crt # 确定证书路径SSLCertificateKeyFile /etc/httpd/ssl/xxxxx.com.key # 确定私钥路径SSLCertificateChainFile /etc/httpd/ssl/root_bundle.crt # 确定证书链路径 以上配置完成后,重启网络服务service network restart,这个时候再访问网站就是https协议了。 参考自SSL 证书 Apache 服务器 SSL 证书安装部署(Linux)-证书安装-文档中心-腾讯云 (tencent.com) 需要注意下,证书到期之后不会自动部署…一般这种免费的SSL证书时间都是1年,在到期前一个月会有提醒,申请完成之后直接覆盖快到期的原证书即可。(省下了一笔自动部署的90块钱) 特别注意SSL证书部署后都是立即生效的,如果你发现网站仍然提示非安全连接,可以看看自己是否用了其他第三方加速。 比如你的网站用了CDN加速,需要同时在第三方加速平台上进行HTTPS配置更改,否则SSL证书无法生效! 比如我这里用了又拍云的CDN加速,且设置了HTTPS访问,不更改成新申请的证书就无法生效。每个CDN加速平台设置HTTPS方法不一样,这里就不详细说了。","categories":[{"name":"个人主页","slug":"个人主页","permalink":"http://www.shelven.com/categories/%E4%B8%AA%E4%BA%BA%E4%B8%BB%E9%A1%B5/"}],"tags":[{"name":"建站","slug":"建站","permalink":"http://www.shelven.com/tags/%E5%BB%BA%E7%AB%99/"}]},{"title":"基因组注释(2)——散在重复序列注释","slug":"基因组注释(2)——散在重复序列注释","date":"2023-03-16T15:13:10.000Z","updated":"2023-07-10T14:40:07.000Z","comments":true,"path":"2023/03/16/a.html","link":"","permalink":"http://www.shelven.com/2023/03/16/a.html","excerpt":"前面我们注释了串联重复序列(Tandem repeat,TR),接下来是对散在重复序列(也称转座子,transposable element,TE)进行注释。注释之后我们对所有重复序列在基因组上进行屏蔽,就可以进行后面的结构基因预测和注释了。","text":"前面我们注释了串联重复序列(Tandem repeat,TR),接下来是对散在重复序列(也称转座子,transposable element,TE)进行注释。注释之后我们对所有重复序列在基因组上进行屏蔽,就可以进行后面的结构基因预测和注释了。 1. 散在重复序列散在重复序列可以分为反转录转座子(class-I TEs)和DNA转座子(class-II TEs) 反转录转座子:通过RNA介导的copy and paste机制进行转座,主要由LTR(long terminal repeat)构成,而non-LTR根据长度又分为LINEs(long interspersed nuclear elements)和SINEs(short interspersed elements)。 DNA转座子:通过DNA介导的cut and paste机制进行转座。 这里我们用RepeatModeler和RepeatMasker两个软件跑一遍基因组散在重复序列注释的流程,需要注意下因为前面做了TRF注释串联重复序列,我们运行RepeatMasker的时候要改下下参数设置。 2. RepeatModeler和RepeatMasker安装不建议用conda安装两款软件的本体(但是可以安装其他依赖) RepeatMasker配置成功过是RepeatModeler配置的前提条件,且两者之间有版本关联(比如最新的RepeatModeler版本为2.0.4,需要最新的RepeatMasker版本4.1.4安装为前提),conda直接安装RepeatMasker会导致RepeatModeler无法找到RepeatMasker的路径,且输入正确路径也会提示找不到(不知道是不是我的原因)。 下载源码包编译,可以看官网RepeatMasker Home Page。 本篇博客所使用RepeatMasker版本为4.1.2,RepeatModeler版本为2.0.3 2.1 RepeatMasker安装本体安装过程不多说,主要说一下加载Repbase数据库: RepeatMasker自带的重复序列数据库是Dfam数据库,这是一个转座子(TE)序列数据库,收录的物种比较少。Repbase是重复序列参考数据库,其中收录了大部分真核物种,适用于重复序列的同源预测。然而Repbase不是RepeatMasker自带的,需要额外下载,我这里提供20181026版本的Repbase下载地址:点击这里 下载Repbase数据库后用tar -xvf解压,将RMRBSeqs.embl和README.RMRBSeqs两个数据库文件放在RepeatMasker安装目录的Libraries目录下,注意不要修改后缀名。 在RepeatMasker安装目录下运行perl ./configure,一路回车确定路径,如果有缺失的依赖就用conda下载,一直到最后选择序列搜索比对的软件,我这里输入3回车,之后的界面再输入5回车确认: 当看到提示信息Dfam和RBRM(也就是RepBase数据库)两个数据库版本的时候,就说明加载Repbase数据库成功了。 用RepeatMasker -h查看是否可以正常运行,如果提示Devel::Size这个perl模块缺失,可以用conda安装: 1conda install -c bioconda perl-devel-size 最后需要修改一下环境变量(不修改运行的时候找不到pm文件),将RepeatMasker 安装路径添加到PERL5LIB环境变量中: 12# 打开 ~/.bashrcexport PERL5LIB="/public/home/wlxie/miniconda3/envs/biosoft/share/RepeatMasker:$PERL5LIB" 2.2 RepeatModeler安装安装过程与RepeatMasker差不多,有一个比较坑的地方是官方可选的一部分软件(比如CD-HIT)在configure过程中是必须指定的,所以还是按照github上的说明将所有依赖都用conda安装好。Dfam-consortium/RepeatModeler: De-Novo Repeat Discovery Tool (github.com) 接下来在RepeatModeler安装的目录下运行perl ./configure,同样是一路回车到底确定路径,最后会询问是否需要预测LTR结构,因为我在之前的求LAI指数的博客中已经做过LTR预测,因此这一步选择n跳过,后续我会说明如何利用LTR预测数据: 3. TE注释策略因为我要注释的生物是非模式生物,在Dfam库和Repbase库中均没有该物种信息(无法在RepeatMasker软件中指定特定的物种,-species 和 -lib的参数是冲突的,需要自建数据库),因此注释所用的数据库将由以下三种数据库组成: LTR_retriever整合的LTR预测数据库(见这篇博客) 同源的(指该类群祖先和衍生节点)重复序列数据库 使用RepeatModeler从头预测序列,训练该物种的重复序列模型,构建预测的重复序列数据库 需要注意这三种数据库都需要fasta格式,将三种数据库合并之后,使用RepeatMasker -lib指定自建数据库,预测TE序列。 4. 注释流程4.1 导出同源物种重复序列库前面2.1步骤将Repbase和Dfam数据库整合之后,RepeatMasker/Libraries目录下RepeatMaskerLib.h5这个文件为整合后构建的数据库文件,我们要在这个文件中导出同源物种的重复序列。 在RepeatMasker目录下提供了famdb.py这个程序查询目标近缘物种。如果你不知道自己的物种在什么分支上,我这里推荐一个查找已发表的植物基因组的网站Published Plant Genomes (plabipd.de),可以一级一级查看哪些近缘物种有人做过了。用以下命令查看物种重复序列否收录到库中: 1python famdb.py -i Libraries/RepeatMaskerLib.h5 lineage -ad lamiids # lamiids是我能查找到的最近的分支 找到最近的分支后,导出最近分支的祖先节点和衍生节点物种的重复序列库,使用内置的perl软件转换成fasta格式: 1234python famdb.py -i Libraries/RepeatMaskerLib.h5 families -f embl -a -d lamiids > lamiids.embl # 查找近缘物种及其上祖先节点,其下所有类群repeat famlies,输出格式embl。 -a ancestor,-d descendentbuildRMLibFromEMBL.pl lamiids.embl > lamiids.fasta # 转换格式为fasta,方便后续合并 4.2 RepeatModeler从头预测新建一个目录,用于存放RepeatModeler的预测结果,写一个repeatmodeler.slurm脚本: 1234567#!/bin/bash#SBATCH -n 100#SBATCH -t 7200BuildDatabase -name luobuma -engine ncbi /public/home/wlxie/NextPolish/luobuma_rundir/genome.nextpolish.fasta # 用基因组组装结果构建数据库RepeatModeler -pa 25 -database luobuma -engine ncbi # 自训练 RepeatModeler以自身基因组数据做训练集,用三种重复序列分析软件( RECON, RepeatScout 和 LtrHarvest/Ltr_retriever)进行预测,最后给出de novo预测结果。需要i说明一下,程序结束之后会给出如下四个文件: sample-families.fa de novo预测重复序列家族文件,也就是预测的重复序列库 sample-familes.stk Seed alignments RM_123456.XXXXXXXXX 中间文件(记录每一轮训练的流程和结果,仅用于中间程序崩了以后可以识别并继续跑流程) sample-rmod.log log文件 最终得到的luobuma-families.fa文件是我们需要的,里面记录了各种de novo预测的重复序列家族。中间文件具体有什么可以参考官方的github文档,这里仅仅是起到Recover from a failure的作用,中间程序没有崩就不用管它。 注意下RepeatModeler -pa参数,1 pa可以运行4个线程,我申请了100个核,这里就是25 pa可以用完所有资源。 这一步运行时间最久,100个核对200Mbp大小的植物基因组进行de novo预测重复序列,跑了17个小时。 4.3 整合数据库将4.1、4.2步骤的结果,以及前面做的LTR预测结果进行整合(都是fasta格式): 1cat lamiids.fasta luobuma-families.fa luobuma.fasta.mod.LTRlib.fa > final_luobuma_repeat.fasta # 合并同源数据库、RepeatModeler训练结果和LTR预测结果 此时得到的final_luobuma_repeat.fasta就是后一步运行RepeatMarsker需要指定的自建数据库。 4.4 RepeatMasker搜索重复序列根据需求确定参数,写一个repeatmasker.slurm脚本: 12345#!/bin/bash#SBATCH -n 100#SBATCH -t 7200RepeatMasker -nolow -no_is -pa 25 -lib final_luobuma_repeat.fasta -engine ncbi -gff -norna -dir luobuma /public/home/wlxie/NextPolish/luobuma_rundir/luobuma.fasta RepeatMasker的参数非常多,介绍一下这里用到的: -nolow Does not mask low_complexity DNA or simple repeats 不屏蔽低复杂度DNA或简单重复序列(有的学者认为simple repeat不算严格意义上的重复序列类型) -norna Does not mask small RNA (pseudo) genes 不屏蔽sRNA -no_is Skips bacterial insertion element check 跳过细菌插入元件检查 -pa 和RepeatModeler一样,1 pa是4个线程 -lib 指定自建数据库(与-species冲突) -gff 生成gff文件 -dir 指定输出目录 在输出目录下可以找到以下几种格式的文件: sample.fasta.cat.gz 基因组和数据库中参考重复序列比对详情,i代表碱基转换,v代表碱基颠换 sample.fasta.masked 重复序列屏蔽我iN后的序列 sample.fasta.out 预测的重复序列详细信息,Smith-Waterman 算法得分等等 sample.fasta.out.gff 上一个文件的gff格式 sample.fasta.tbl RepeatMasker的结果报告 主要看一下结果报告: TE的预测结果被分为逆转录转座子、DNA转座子和Unclassified三类,总的转座子序列数量和在基因组的占比见Total interspersed repeats统计结果。做到这里可以再结合前面做的TR分析,做一个基因组重复序列注释汇总表,我这里就不再演示了。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"RepeatModeler","slug":"RepeatModeler","permalink":"http://www.shelven.com/tags/RepeatModeler/"},{"name":"RepeatMasker","slug":"RepeatMasker","permalink":"http://www.shelven.com/tags/RepeatMasker/"}]},{"title":"基因组注释(1)——串联重复序列注释","slug":"基因组注释(1)——串联重复序列注释","date":"2023-03-12T15:14:40.000Z","updated":"2023-03-12T15:18:13.000Z","comments":true,"path":"2023/03/12/a.html","link":"","permalink":"http://www.shelven.com/2023/03/12/a.html","excerpt":"本系列笔记开始记录如何对组装的植物基因组进行注释。前面通过一系列组装过程,我们拿到了组装好的基因组草图,而这个基因组草图只是研究的开始,我们关注的是基因组中有哪些我们感兴趣的功能基因或者结构基因,以及怎么用这些基因阐述生物学问题等等,这个时候一个高准确度的基因组注释结果就非常重要了。","text":"本系列笔记开始记录如何对组装的植物基因组进行注释。前面通过一系列组装过程,我们拿到了组装好的基因组草图,而这个基因组草图只是研究的开始,我们关注的是基因组中有哪些我们感兴趣的功能基因或者结构基因,以及怎么用这些基因阐述生物学问题等等,这个时候一个高准确度的基因组注释结果就非常重要了。 基因组注释可以分为两部分:基因组的结构注释(重复序列识别、非编码基因预测、编码基因预测)和基因功能注释,结构注释是功能注释的基础。 这里先从结构注释中的重复序列注释开始。我们知道植物基因组多倍化频繁,且基因组中存在大量的重复序列(有的植物基因组中重复序列甚至能达到80%),这些重复序列控制植物表型调控中有非常重要的作用。基因组中的重复序列可以分为以下几种: 1. 串联重复序列注释串联重复(Tandem Repeat, TR)指DNA中的一个或多个核苷酸前后相连接的重复。串联重复又分为卫星DNA(Satellite DNA)、小卫星(Minisatellite)、微卫星(Microsatellite)。微卫星在植物中一般称为SSR(Simple Sequence Repeats)SSR在植物基因组常被用做遗传标记使用。下面我用两款软件跑下串联重复序列注释。 1.1 GMATA这个软件主要用来搜索重复单元较短的简单重复序列,也就是微卫星SSR序列。这软件运行速度比较快,而且可以同时设计SSR引物,还可以预测elect-PCR结果,或者将预测结果显示在基因组浏览器上,可以在github上找到项目地址:XuewenWangUGA/GMATA: software GMATA (github.com) 需要注意如果是在linux系统上跑一键流程的话,需要单独安装primer3和e-pcr(可以直接用conda安装),分别是设计SSR引物和模拟PCR的时候需要调用。如果没有这方面需要,可以在设置文件default_cfg.txt修改为不启用后面的模块。我这里统一用linux系统演示,只演示命令行操作,这个软件在windows上运行有UI界面,还是比较直观的。 12345#!/bin/bash#SBATCH -n 50#SBATCH -t 7200perl gmata.pl -c default_cfg.txt -i /public/home/wlxie/NextPolish/luobuma_rundir/genome.nextpolish.fasta 我这里直接用了一键流程,修改默认的设置文件中三个模块[set]:doprimer_smt、[set]:elctPCR和[set]:mk2gff3的ModulRun = N,虽然可以批量设计引物,但是我这里用不着….. 预测的SSR结果在原fasta文件路径下,以.ssr和.ssr.sat2为后缀: 在sat结果文件中,最终结果以4个表格的方式呈现,分别统计motif k-mer、motif和成对的motif信息以及最后每个contig的SSR统计信息。以上是其中两个表格。 1.2 TRF这个软件和上面软件类似,可以统计整个基因组上的串联重复序列,在上面一个软件输出结果上稍微有些不同。 12345#!/bin/bash#SBATCH -n 50#SBATCH -t 7200trf /public/home/wlxie/NextPolish/luobuma_rundir/genome.nextpolish.fasta 2 7 7 80 10 50 500 -f -d -m -r -h 说明一下这个软件使用过程中传参的一堆数字代表什么: 12345678910111213Match = matching weight # 匹配上的权重,缺省值2Mismatch = mismatching penalty # 未匹配上的权重,缺省值7Delta = indel penalty # 插入罚分,缺省值7PM = match probability (whole number) # 比对上的概率,可选值为80和75PI = indel probability (whole number) # 插入的概率,可选值为10和20Minscore = minimum alignment score to report # 串联重复序列的比对必须达到或超过要报告的比对分数MaxPeriod = maximum period size to report # 最大重复单元的bp数,不指定的话从1到2000其他主要可选参数(列一部分):-m 输出屏蔽重复序列后的基因组-f 记录每个重复序列侧翼的500个核苷酸,主要用于PCR引物设计-d 生成屏蔽数据文件,与汇总表有相同的信息,不包含标签,主要方便做其他处理-h 不生成html结果文件(contig数量多的话建议使用,否则有大量的文件生成) 运行结束后可以生成.mask后缀的屏蔽后的序列文件,还有一个.dat后缀的结果文件,包含了重复序列的详细信息。 主要讲解下这个dat文件,先less -S打开看看: 上面记录的参数和其他信息就不说了,主要是底下的数据,每一行是一个重复序列的信息,每行分为15列: 第1列和第2列是预测到的重复序列的起始和结束位点; 第3列是重复单元的长度; 第4列是重复单元的拷贝数,不一定是整数,因为可能存在插入缺失; 第5列是一致性序列的长度; 第6列是匹配的百分率; 第7列是插入缺失的百分率; 第8列是TRF软件给的分值,越高越可靠; 第9-12列分别为ACGT碱基的个数; 第13列表示比对的熵值; 第14列是一致性序列的具体碱基排列; 第15列是整个重复序列的具体碱基排列顺序。 对于结果文件的处理有两种方法,一种是将.dat后缀的结果文件转换成标准的.gff3文件格式,然后用bedtools提取trf特征。转化gff的的方法github上有不少开源的项目,这里推荐一个Adamtaranto/TRF2GFF: Convert Tandem Repeat Finder dat file output into gff3 format (github.com) 还有一种方法就是自己写个脚本,可以看到同一位点处可能有多条预测的串联重复序列,也就是说这些串联重复序列之间可能存在交叠,思路是将同一位点预测的重复序列只保留最短的一条(起始位点相同保留前一条,结束位点相同保留后一条),然后统计第3列重复序列k-mer数量和类型,根据第1列和第2列计算长度,统计总长度和占比即可。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394'''自编TFR dat格式结果文件统计脚本2023.3.12'''from collections import Counter# 数据过滤loci_start = []loci_finish = []total_line = []with open('./genome.baima.fasta.2.7.7.80.10.50.500.dat', 'r') as input: for i in input.readlines()[15:]: if i.find('Sequence') != -1 or i.find('Parameters') != -1: continue else: lst = i.strip().split(' ') if len(lst) < 15: continue if len(loci_start) < 1 and len(loci_finish) < 1: # 处理列表为空的情况 loci_start.append(lst[0]) loci_finish.append(lst[1]) total_line.append(lst) else: if lst[0] != loci_start[-1] and lst[1] != loci_finish[-1]: # 开始结束位点都不同,则记录数据 loci_start.append(lst[0]) loci_finish.append(lst[1]) total_line.append(lst) elif lst[0] == loci_start[-1]: # 开始位点相同,跳过 continue elif lst[1] == loci_finish[-1]: # 结束位点相同,删除列表最后一个元素并加入新元素 del loci_start[-1] del loci_finish[-1] del total_line[-1] loci_start.append(lst[0]) loci_finish.append(lst[1]) total_line.append(lst)# 提取motif长度和重复序列长度motif_lst = []leng_lst = []for i in total_line: motif_lst.append(i[2]) leng = int(i[1]) - int(i[0]) + 1 leng_lst.append(leng)# 统计相同motif的总长度和所有重复序列总长度total_leng = {}motif_sum = 0for i in range(len(motif_lst)): item = motif_lst[i] motif_sum += leng_lst[i] if item in total_leng: total_leng[item] += leng_lst[i] else: total_leng[item] = leng_lst[i]# 统计motif-mer数量,总数,占比count_motif = Counter(motif_lst)count_lst = list(count_motif.items())count_lst.sort(key = lambda x : x[1], reverse = True)lst_ = []hit_num = 0for i in count_lst: hit_num += i[1] for i in count_lst: ls = i[0] lst1 = list(i) if ls in total_leng: lst1.append(total_leng[ls]) precentage = '%.2f%%'%(100 * i[1] / hit_num) lst1.append(precentage) lst_.append(lst1)# 统计结果过滤(取前二十)lst_filted = []hit_ = 0motif_ = 0for i in range(1, 20): lst_filted.append(lst_[i]) hit_ += lst_[i][1] motif_ += lst_[i][2]lst_filted.append(['Other', int(hit_num - hit_), int(motif_sum - motif_), '%.2f%%'%(100 - 100 * hit_ / hit_num)])lst_filted.append(['Total', int(hit_num), int(motif_sum), '100%'])# 输出结果with open('./stastics.xls', 'w') as output: output.write('Motif(-mer)\\tNumber\\tLength(bp)\\tPrecentage\\n') for i in lst_filted: motif = i[0] number = i[1] length = i[2] pre = i[3] output.write(motif + '\\t' + str(number) + '\\t' + str(length) + '\\t' + str(pre) + '\\n') **写的有点冗长,能实现统计功能就行…..**实现的结果如下: 可以看到两个软件统计结果还是有比较大的出入的,可能是在算法上有不同。在单独分析SSR序列的时候还是用GMATA准确一些,如果是统计全基因组上的串联重复序列则使用老牌的TRF更为合适。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"GMATA","slug":"GMATA","permalink":"http://www.shelven.com/tags/GMATA/"},{"name":"TRF","slug":"TRF","permalink":"http://www.shelven.com/tags/TRF/"}]},{"title":"绕过双重封锁部署ChatGPT到zhenxun_bot","slug":"绕过双重封锁部署ChatGPT到zhenxun-bot","date":"2023-03-08T13:54:56.000Z","updated":"2023-03-08T14:01:24.000Z","comments":true,"path":"2023/03/08/a.html","link":"","permalink":"http://www.shelven.com/2023/03/08/a.html","excerpt":"作为一个从ChatGPT公测用到现在的用户,有些无奈很难言说。本来OpenAI就不对咱们这个区域开放,使用官方的API搭建应用可以不借助VPN访问,算是解除了区域限制。但是,从2023年3月2日傍晚开始,API接口就开始没有响应了,官网没有问题,四处查询发现可能是API的域名上了GFW名单(暂不确定,有可能重大会议过去后会恢复?)。","text":"作为一个从ChatGPT公测用到现在的用户,有些无奈很难言说。本来OpenAI就不对咱们这个区域开放,使用官方的API搭建应用可以不借助VPN访问,算是解除了区域限制。但是,从2023年3月2日傍晚开始,API接口就开始没有响应了,官网没有问题,四处查询发现可能是API的域名上了GFW名单(暂不确定,有可能重大会议过去后会恢复?)。 因此,现在摆在眼前的问题是如何绕过双重封锁调用OpenAI的API接口?最稳妥的方式当然是给服务器挂个全局代理,但是我的服务器本身就在作代理服务器,给服务器再上个代理会比较麻烦……这里记录下自己实现的方式,顺便记录下是如何部署ChatGPT到zhenxun_bot(这个bot真的超级好用!)上的。 本人在这方面是小白,只是记录实现过程。 此部分内容需要以部署zhenxun_bot为前提、有一个未上GFW名单的域名(国内需要实名)。 1. 部署ChatGPT到zhenxun_bot时间过去太久,已经找不到写插件的原作者了…我是在原插件的基础上copy和修改了一部分代码,实际上就是在zhenxun_bot的AI插件基础上做的一点删改(可能有些没删干净,懒得查了)。如果没有修改路径的话,原文件路径是/zhenxun_bot/plugins/ai/data_source.py,下面代码替代原文件内容(原文件最好另存以防万一): 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238import osimport randomimport refrom utils.http_utils import AsyncHttpxfrom configs.path_config import IMAGE_PATH, DATA_PATHfrom services.log import loggerfrom utils.message_builder import image, facefrom configs.config import Config, NICKNAMEfrom .utils import ai_message_managerfrom copy import deepcopyfrom transformers import GPT2TokenizerFastimport openaiopenai.api_key = "xxxxxxxxxxxxxx"try: import ujson as jsonexcept ModuleNotFoundError: import jsonsession_config = { 'preset': '你是一个大型语言模型,可以回答我的问题。如果我有任何问题,请随时告诉你,你会尽力为我解答。', 'context': ''}sessions = {}tokenizer = GPT2TokenizerFast.from_pretrained("gpt2-large")check_url = "https://v2.alapi.cn/api/censor/text"index = 0anime_data = json.load(open(DATA_PATH / "anime.json", "r", encoding="utf8"))# 获取对话sessiondef get_chat_session(sessionid): if sessionid not in sessions: config = deepcopy(session_config) config['id'] = sessionid sessions[sessionid] = config return sessions[sessionid] def chat_with_gpt(prompt): try: resp = openai.Completion.create( model = "text-davinci-003", temperature = 0.9, max_tokens=3000, top_p=1, presence_penalty=0, frequency_penalty=0, prompt=prompt) resp = resp['choices'][0]['text'] except openai.OpenAIError as e: print('openai 接口报错: ' + str(e)) resp = str(e) return respasync def get_chat_result(text: str, img_url: str, user_id: int, nickname: str) -> str: """ 获取 AI 返回值,顺序: 特殊回复 -> GPT3 -> 青云客 :param text: 问题 :param img_url: 图片链接 :param user_id: 用户id :param nickname: 用户昵称 :return: 回答 """ global index ai_message_manager.add_message(user_id, text) special_rst = await ai_message_manager.get_result(user_id, nickname) if special_rst: ai_message_manager.add_result(user_id, special_rst) return special_rst if index == 5: index = 0 if len(text) < 6 and random.random() < 0.6: keys = anime_data.keys() for key in keys: if text.find(key) != -1: return random.choice(anime_data[key]).replace("你", nickname) rst = await GPT_3(text, user_id) if not rst: rst = await xie_ai(text) if not rst: return no_result() if nickname: if len(nickname) < 5: if random.random() < 0.5: nickname = "~".join(nickname) + "~" if random.random() < 0.2: if nickname.find("大人") == -1: nickname += "大~人~" rst = str(rst).replace("小主人", nickname).replace("小朋友", nickname) ai_message_manager.add_result(user_id, rst) return rst# GPT3接口async def GPT_3(msg: str, sessionid: int) -> str: """ 获取GPT3接口的回复 指令如下(群内需@机器人):1.[重置会话] 请发送 重置会话2.[设置人格] 请发送 设置人格+人格描述3.[重置人格] 请发送 重置人格。 注意:重置会话不会清空人格,重置人格会重置会话!设置人格后人格将一直存在,除非重置人格或重启逻辑端! """ try: if msg.strip() == '': return '您好,我是人工智能助手,如果您有任何问题,请随时告诉我,我将尽力回答。\\n如果您需要重置我们的会话,请回复`重置会话`' # 获得对话session session = get_chat_session(sessionid) if '重置会话' == msg.strip(): session['context'] = '' return "会话已重置" if '重置人格' == msg.strip(): session['context'] = '' session['preset'] = session_config['preset'] return '人格已重置' if msg.strip().startswith('设置人格'): session['preset'] = msg.strip().replace('设置人格', '') session['context'] = '' # 处理上下文逻辑 token_limit = 4096 - 3000 - len(tokenizer.encode(session['preset'])) - 3 session['context'] = session['context'] + "\\n\\nQ:" + msg + "\\nA:" ids = tokenizer.encode(session['context']) tokens = tokenizer.decode(ids[-token_limit:]) # 计算可发送的字符数量 char_limit = len(''.join(tokens)) session['context'] = session['context'][-char_limit:] # 从最早的提问开始截取 pos = session['context'].find('Q:') session['context'] = session['context'][pos:] # 设置预设 msg = session['preset'] + '\\n\\n' + session['context'] print(msg) # 与ChatGPT交互获得对话内容 message = chat_with_gpt(msg) print("会话ID: " + str(sessionid)) print("ChatGPT返回内容: ") print(message) return message except Exception as error: traceback.print_exc() return str('异常: ' + str(error)) # 屑 AIasync def xie_ai(text: str) -> str: """ 获取青云客回复 :param text: 问题 :return: 青云可回复 """ res = await AsyncHttpx.get(f"http://api.qingyunke.com/api.php?key=free&appid=0&msg={text}") content = "" try: data = json.loads(res.text) if data["result"] == 0: content = data["content"] if "菲菲" in content: content = content.replace("菲菲", NICKNAME) if "艳儿" in content: content = content.replace("艳儿", NICKNAME) if "公众号" in content: content = "" if "{br}" in content: content = content.replace("{br}", "\\n") if "提示" in content: content = content[: content.find("提示")] if "淘宝" in content or "taobao.com" in content: return "" while True: r = re.search("{face:(.*)}", content) if r: id_ = r.group(1) content = content.replace( "{" + f"face:{id_}" + "}", str(face(int(id_))) ) else: break return ( content if not content and not Config.get_config("ai", "ALAPI_AI_CHECK") else await check_text(content) ) except Exception as e: logger.error(f"Ai xie_ai 发生错误 {type(e)}:{e}") return ""def hello() -> str: """ 一些打招呼的内容 """ result = random.choice( ( "哦豁?!", "你好!Ov<", f"库库库,呼唤{NICKNAME}做什么呢", "我在呢!", "呼呼,叫俺干嘛", ) ) img = random.choice(os.listdir(IMAGE_PATH / "zai")) if img[-4:] == ".gif": result += image(img, "zai") else: result += image(img, "zai") return result# 没有回答时回复内容def no_result() -> str: """ 没有回答时的回复 """ return ( random.choice( [ "你在说啥子?", f"纯洁的{NICKNAME}没听懂", "下次再告诉你(下次一定)", "你觉得我听懂了吗?嗯?", "我!不!知!道!", ] ) + image(random.choice(os.listdir(IMAGE_PATH / "noresult")), "noresult") )async def check_text(text: str) -> str: """ ALAPI文本检测,主要针对青云客API,检测为恶俗文本改为无回复的回答 :param text: 回复 """ if not Config.get_config("alapi", "ALAPI_TOKEN"): return text params = {"token": Config.get_config("alapi", "ALAPI_TOKEN"), "text": text} try: data = (await AsyncHttpx.get(check_url, timeout=2, params=params)).json() if data["code"] == 200: if data["data"]["conclusion_type"] == 2: return "" except Exception as e: logger.error(f"检测违规文本错误...{type(e)}:{e}") return text openai.api_key需要上官网获取后填入 实际上就是把原来的图灵接口替换成GPT3接口。引入openai库和transformers库,使用了前者的openai.Completion.create()方法和后者GPT2TokenizerFast.from_pretrained()预训练的GPT2模型和分词器。 关键在于前者,因为OpenAI的API网站已经上了GFW名单,所以我们现在就算有api_key也无法调用API接口(会显示超时)。以下是解决方法。 2. 托管域名到CLOUDFLARE后面我们要用到CLOUDFLARE,没有账户的话注册一个:https://dash.cloudflare.com/ 注册之后点击右边的Websites,按照操作流程添加主域名,修改两个DNS服务器名字。 比如我这里用阿里云买了一个域名,需要登录阿里云的域名控制台,点击管理,进入右边DNS修改页面 修改原来默认的DNS服务器为lorna.ns.cloudflare.com和ram.ns.cloudflare.com。 中间可能还需要你邮件确认,按照提示操作就可以。 3. 创建CLOUDFLARE Workers该步骤参考来自github [noobnooc],感谢大佬提供的解决方案! 回到CLOUDFLARE,点击右边的创建Workers——Create a Service,这里直接确认创建一个服务。 创建之后点击Quick edit修改workers代码如下(起到代理api.openai.com的作用): 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156// Website you intended to retrieve for users.const upstream = 'api.openai.com'// Custom pathname for the upstream website.const upstream_path = '/'// Website you intended to retrieve for users using mobile devices.const upstream_mobile = upstream// Countries and regions where you wish to suspend your service.const blocked_region = []// IP addresses which you wish to block from using your service.const blocked_ip_address = ['0.0.0.0', '127.0.0.1']// Whether to use HTTPS protocol for upstream address.const https = true// Whether to disable cache.const disable_cache = false// Replace texts.const replace_dict = { '$upstream': '$custom_domain',}addEventListener('fetch', event => { event.respondWith(fetchAndApply(event.request));})async function fetchAndApply(request) { const region = request.headers.get('cf-ipcountry').toUpperCase(); const ip_address = request.headers.get('cf-connecting-ip'); const user_agent = request.headers.get('user-agent'); let response = null; let url = new URL(request.url); let url_hostname = url.hostname; if (https == true) { url.protocol = 'https:'; } else { url.protocol = 'http:'; } if (await device_status(user_agent)) { var upstream_domain = upstream; } else { var upstream_domain = upstream_mobile; } url.host = upstream_domain; if (url.pathname == '/') { url.pathname = upstream_path; } else { url.pathname = upstream_path + url.pathname; } if (blocked_region.includes(region)) { response = new Response('Access denied: WorkersProxy is not available in your region yet.', { status: 403 }); } else if (blocked_ip_address.includes(ip_address)) { response = new Response('Access denied: Your IP address is blocked by WorkersProxy.', { status: 403 }); } else { let method = request.method; let request_headers = request.headers; let new_request_headers = new Headers(request_headers); new_request_headers.set('Host', upstream_domain); new_request_headers.set('Referer', url.protocol + '//' + url_hostname); let original_response = await fetch(url.href, { method: method, headers: new_request_headers, body: request.body }) connection_upgrade = new_request_headers.get("Upgrade"); if (connection_upgrade && connection_upgrade.toLowerCase() == "websocket") { return original_response; } let original_response_clone = original_response.clone(); let original_text = null; let response_headers = original_response.headers; let new_response_headers = new Headers(response_headers); let status = original_response.status; if (disable_cache) { new_response_headers.set('Cache-Control', 'no-store'); } new_response_headers.set('access-control-allow-origin', '*'); new_response_headers.set('access-control-allow-credentials', true); new_response_headers.delete('content-security-policy'); new_response_headers.delete('content-security-policy-report-only'); new_response_headers.delete('clear-site-data'); if (new_response_headers.get("x-pjax-url")) { new_response_headers.set("x-pjax-url", response_headers.get("x-pjax-url").replace("//" + upstream_domain, "//" + url_hostname)); } const content_type = new_response_headers.get('content-type'); if (content_type != null && content_type.includes('text/html') && content_type.includes('UTF-8')) { original_text = await replace_response_text(original_response_clone, upstream_domain, url_hostname); } else { original_text = original_response_clone.body } response = new Response(original_text, { status, headers: new_response_headers }) } return response;}async function replace_response_text(response, upstream_domain, host_name) { let text = await response.text() var i, j; for (i in replace_dict) { j = replace_dict[i] if (i == '$upstream') { i = upstream_domain } else if (i == '$custom_domain') { i = host_name } if (j == '$upstream') { j = upstream_domain } else if (j == '$custom_domain') { j = host_name } let re = new RegExp(i, 'g') text = text.replace(re, j); } return text;}async function device_status(user_agent_info) { var agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"]; var flag = true; for (var v = 0; v < agents.length; v++) { if (user_agent_info.indexOf(agents[v]) > 0) { flag = false; break; } } return flag;} 修改之后点击右下角Save and deploy,此时worker地址还不能直接代替openai的API地址,需要进一步绑定前面的域名(CLOUDFLARE Workers只能绑定托管到CLOUDFLARE的域名,所以有了前面一步)。 4. 绑定域名点击Workers进入管理页面,点击Triggers——Add Custom Domain,将前面托管的域名填进去,可以用自己喜欢的二级域名: 大约过几分钟,custom domains显示Certificate 为 Activate即可。 这个时候就可以通过你绑定的域名来访问api.openai.com了,可以通过其他POST工具调试接口,就不多说了。 5. 修改openai库前面做的一系列步骤是让你可以通过其他域名访问openai的API网站,但是前面第一步写的插件调用了openai.Completion.create()方法函数,此时仍然会直接访问api.openai.com,这个时候就是扒源代码修改了。 locate openai先找到服务器上openai下载的位置,在对应的路径修改,比如我的文件路径是/root/anaconda3/lib/python3.9/site-packages/openai,修改该路径下的__init__.py文件: 第34行api_base后面的网址改为刚刚绑定的网址(/v1的部分不要动)。 上述步骤完成后,重启zhenxun_bot就可以在不对自己服务器做任何代理的情况下正常调用OpenAI的API接口了。 顺便说一下,2022年12月申请的openAI账号每个账户有18美元的额度,现在(2023年3月)申请的账号就只有5美元额度了,emmmmmmm… 不过上面的那个插件用的是text-davinci-003模型,和ChatGPT用的模型稍有不同,就在前几天ChatGPT公开了API,所使用的模型为gpt-3.5-turbo。并且我前面用的方法是Create completion,和ChatGPT创建实例的方法Create chat completion是不同的,且收费也不一样,现在ChatGPT API收费标准是0.002美元/1000 tokens,token数和字数是不一样的,要看分词器怎么分,不过现在API输出上限均为4096 token。有空再更新一下模型方法,这里只是做个记录。 详细的API调用方法需要参考官网API Reference - OpenAI API","categories":[{"name":"网络相关","slug":"网络相关","permalink":"http://www.shelven.com/categories/%E7%BD%91%E7%BB%9C%E7%9B%B8%E5%85%B3/"},{"name":"QQ机器人","slug":"QQ机器人","permalink":"http://www.shelven.com/categories/QQ%E6%9C%BA%E5%99%A8%E4%BA%BA/"}],"tags":[{"name":"ChatGPT","slug":"ChatGPT","permalink":"http://www.shelven.com/tags/ChatGPT/"}]},{"title":"0基础学习三代基因组测序组装(9)——GATK检测植物基因组SNP和INDEL变异","slug":"0基础学习基因组三代测序组装(9)——GATK检测植物基因组SNP和INDEL变异","date":"2023-03-07T09:53:32.000Z","updated":"2023-03-07T09:56:00.000Z","comments":true,"path":"2023/03/07/a.html","link":"","permalink":"http://www.shelven.com/2023/03/07/a.html","excerpt":"基因组组装完成之后,我们就可以对基因组进行变异分析了。这里主要介绍由 Broad Institute开发的一款基因组分析工具GATK,这款工具设计之初是用于处理分析Illumina二代测序技术产生的人类全外显子和全基因组数据,经过多个版本的优化迭代,GATK集合了多种高通量测序数据处理和质控的软件,如今GATK可以说是对DNA和RNA-seq数据检测SNP和Indel的标准。","text":"基因组组装完成之后,我们就可以对基因组进行变异分析了。这里主要介绍由 Broad Institute开发的一款基因组分析工具GATK,这款工具设计之初是用于处理分析Illumina二代测序技术产生的人类全外显子和全基因组数据,经过多个版本的优化迭代,GATK集合了多种高通量测序数据处理和质控的软件,如今GATK可以说是对DNA和RNA-seq数据检测SNP和Indel的标准。 1. GATK安装GATK的运行依赖于JAVA环境,目前(2023年3月6日)GATK更新到版本4.3.0,可以直接用conda下载。为了避免环境冲突,最好创建一个新环境专门用于GATK和相关变异检测工具运行。 12345conda create -n GATKconda install -c bioconda gatkconda install bwa-mem2conda install samtools bwa-mem2和samtools用于双端序列比对回基因组,需要单独下载这两个软件,后面再说。 安装完成之后可以通过gatk --help查看是否正常。 2. 流程详解流程内容主要参考官网Best Practices Workflows的文章。 GATK工具的变异注释主要包括3个部分:数据预处理(Data Pre-processing)、变异检测(Variant Discovery)和变异优化(Callset Refinement)。 以Germline short variant discovery (SNPs + Indels) ,即胚系短变异的发现为例,官网对多个样本(群组数据,Cohort Data)的变异检测分析流程如下: 单个样本(Single-Sample Data)的变异检测标准流程如下: 分析流程基本类似,以我前面组装的三代植物基因组为例跑一下这两个流程,强调一下我用的是植物基因组,后续无法用Germline的注释数据资源对变异集进行功能注释!因此这里只跑到变异集过滤的步骤,拿到SNP和INDEL。 2.1 数据预处理 构建参考基因组索引:组装的基因组作为reference参考基因组,首先需要对参考基因组建立索引,方便后续进行比对和对参考基因组进行查询。注意三个软件的索引文件不同,每个软件都必须建立索引。 1234567#!/bin/bashref=$1bwa-mem2 index $refsamtools faidx $refreferencename=`basename $ref | sed "s/fasta/dict/" ` # .fasta文件后缀改为.dictgatk CreateSequenceDictionary -R $ref -O $referencename fasta文件转化uBAM文件,标记adapter 序列:组装好的基因组格式是fasta格式,需要转换成uBAM格式(umapped的BAM文件),接着标记illumina二代测序的adapter序列。本质上都是调用了Picard工具,也可以直接用java写脚本。 123456#!/bin/bashsampleName=$1gatk FastqToSam -F1 raw_fastq/${sampleName}_1.fq.gz -F2 raw_fastq/${sampleName}_2.fq.gz -RG $sampleName -SM $sampleName -O ubam/${sampleName}.bamgatk MarkIlluminaAdapters -I ubam/${sampleName}.bam -O markadapeters/${sampleName}.markadapeters.bam -M markadapeters/${sampleName}.metrics.txt 标记后的序列转化成fastq格式,回比参考基因组,得到干净的BAM文件:第二和第三步来自官方的文章(How to) Map and clean up short read sequence data efficiently,通过联合SamToFastq、BWA - MEM和MergeBamAlignment三个程序,节省比对时间,绕过占用空间过大的SAM文件,MergeBamAlignment可以将已排序的SAM中的定义信息(我这里将SAM文件转换成BAM文件)与uBAM中的定义信息进行合并,得到干净的BAM并进行排序和构建索引。 123456789101112#!/bin/bashsampleName=$1threads=50ref=/public/home/wlxie/biosoft/GATK_file/gatk/ref/luobuma.fastagatk SamToFastq -I markadapeters/${sampleName}.markadapeters.bam -F interleaved_fq/${sampleName}_1.interleaved.fq.gz -F2 interleaved_fq/${sampleName}_2.interleaved.fq.gz -CLIP_ATTR XT -CLIP_ACT 2bwa-mem2 mem -M -t $threads $ref interleaved_fq/${sampleName}_1.interleaved.fq.gz interleaved_fq/${sampleName}_2.interleaved.fq.gz | samtools view -Sb - > raw_bam/${sampleName}.bamgatk MergeBamAlignment -R $ref -UNMAPPED ubam/${sampleName}.bam -O align_bam/${sampleName}.bam -ALIGNED raw_bam/${sampleName}.bam -MC true --CREATE_INDEX truerm -rf markadapeters/${sampleName}.markadapeters.ba interleaved_fq/${sampleName}_1.interleaved.fq.gz interleaved_fq/${sampleName}_2.interleaved.fq.gz raw_bam/${sampleName}.bam # 删除中间文件 标记重复序列:做SNP分析前最重要的一点就是标记重复序列(mark duplicate),二代测序是在PCR扩增的基础上进行的,因此PCR扩增产生的多拷贝会结合到flowcell的不同位置,生成完全相同的测序cluster,最后得到重复序列。这一步就是标记这些重复序列(但是没有删除,对结果应该不影响),最后得到一个metrics文件(duplicate的统计信息)和一个bam文件(duplicate的详细信息,注意要创建索引)。 123456#!/bin/bashsampleName=$1gatk MarkDuplicates -I align_bam/${sampleName}.bam -O markdup/${sampleName}.markdup.bam -M markdup/${sampleName}.markdup_metrics.txt --CREATE_INDEX truerm -rf align_bam/${sampleName}.bam # 删除中间文件 最终生成的bam文件进行下一步变异检测,可以用samtools -view 查看bam文件的内容(也没啥好看的,感兴趣可以看看之前博客写的sam文件格式解读)。 注意:官网的数据预处理后续还有一步碱基质量重校正BQSR(Base Quality Scores Recallbrate),官方提供了两个工具BaseRecalibrator和ApplyBQSR。第一个工具计算需要校正的reads和特征值,输出校准表文件,需要注意的是,这个工具需要一个或者多个已知且可靠的物种变异位点数据库,比如人类就有千人基因组计划的各种变异位点数据库,1000G_omni2.5.hg38.vcf.gz、dbsnp_146.hg38.vcf.gz等等;第二个工具根据第一个工具生成校准表,重新调整原来BAM文件中的碱基质量值,重新输出到一个新的BAM文件中。 因为我这个植物没有已知的可靠变异位点数据,因此不用做最后这一步。 本部分程序需要运行10小时。 2.2 变异检测因为我只有一个样本,所以可以按照单个样本(Single-Sample Data)的标准流程来做,也可以按照多样本(Cohort Data)的流程做。该部分主要用到的工具是HaplotypeCaller,两个流程只是对HaplotypeCaller工具产生的结果做了不同的处理,最后都是得到包含SNP和Indel的VCF文件。我也将分别跑两个流程来对比下差异。 首先要明白HaplotypeCaller这个工具具体做了什么,是怎么找出单碱基变异的: 1.定义活跃区域(Define active regions):根据是否存在变异来确定需要操作的基因组的活跃区域。 2.通过组装活跃区域确定单倍型(Determine haplotypes by assembly of the active region):对于每个活跃区域,构建一个类似De Bruijn图来重新组装活性区域,识别可能的单倍型,然后用Smith-Waterman算法将每个单倍型与参考基因组单倍型重新比对,发现潜在的变异位点。 3.确定read单倍型似然值(Determine likelihoods of the haplotypes given the read data):对每个活跃区域使用PairHMM算法对每个read与每个单倍型进行两两比对,生成单倍型似然矩阵,将似然值边缘化,以获得每个潜在变异位点的等位基因的可能性。 4.指定样本基因型(Assign sample genotypes):对每个潜在的变异位点使用贝叶斯算法,转化每个位点的基因型的似然值。然后将最可能的基因型指定为样本基因型。 以上流程翻译自HaplotypeCaller – GATK (broadinstitute.org),这个工具可用参数非常之多,下面跑的流程就展示一些常用的。 2.2.1 多样本的SNP和INDEL检测 使用HaplotypeCaller的GVCF模式,找到每个样本SNP和INDEL变异。在GVCF模式下,每个样本的结果文件以gvcf(genomic vcf)格式文件呈现,实际上gvcf格式和vcf格式类似,gvcf记录所有位点的突变情况,并且提供这些位点是否是纯和的置信度,主要还是方便将所有样本的gvcf联合起来方便分析。 123456#!/bin/bashsampleName=$1threads=50ref=/public/home/wlxie/biosoft/GATK_file/gatk/ref/luobuma.fastagatk HaplotypeCaller -R $ref --native-pair-hmm-threads ${threads} --emit-ref-confidence GVCF -I ../../pre_processing/markdup/${sampleName}.markdup.bam -O ${sampleName}.g.vcf.gz 这一步是整个SNP和IDEL检测中运行时间最长,需要算力最多的一步(主要是Pair-HMM算法花时间),Pair-HMM算法在本地调用的线程数是可以更改的,官方是给定默认值为4。GATK4.0版本开始放弃了多线程任务,这个参数可能是更新后遗漏的,因为我这里用的50线程和默认的4线程跑几乎没有区别,都是在28小时左右完成,此处参数存疑。 建库合并所有样本的GVCF文件(单样本不用做这步),并将GVCF文件转化为VCF文件。对于多样本的GVCF合并,现在官方建议用GenomicsDBImport这个工具进行建库合并,速度会快很多。 12345678#!/bin/bashsampleName=$1threads=50ref=/public/home/wlxie/biosoft/GATK_file/gatk/ref/luobuma.fasta# gatk GenomicsDBImport $(for file in `ls *.g.vcf.gz`; do echo "-V $file"; done) --genomicsdb-workspace-path database -L chr01 # 单样本不用做这步,因为就一个GVCF。多样本注意-L参数是建库必须的,根据fasta参考基因组的染色体名称命名,拆分gatk GenotypeGVCFs -R $ref -V ${sampleName}.g.vcf.gz -O raw_variants.vcf.gz # 多样本使用参数-V gendb://database 也就是上一步建的数据库名,单样本直接用gvcf文件 可以看看最后生成的VCF文件: 文件解读放在最后过滤和拆分SNP和INDEL的时候再说,这里是初步得到变异信息,需要经过过滤和筛选。 本部分程序需要运行28小时。 2.2.2 单个样本的SNP和INDEL检测使用HaplotypeCaller默认的single-sample模式,直接生成统计SNP和INDEL变异的VCF文件。 123456#!/bin/bashsampleName=$1threads=50ref=/public/home/wlxie/biosoft/GATK_file/gatk/ref/baima.fastagatk HaplotypeCaller -R $ref --native-pair-hmm-threads ${threads} -I ../../pre_processing/markdup/${sampleName}.markdup.bam -O ${sampleName}.vcf.gz 同上一个步骤,此处参数--native-pair-hmm-threads对运算速度的提升存疑,最后同样是生成VCF文件,运行时间同样为28小时。 2.3 变异过滤(优化)变异集过滤方法主要有两种: 1.软过滤:基于机器学习的方法,对原始vcf文件进行变异质量重矫正和过滤。比如有基于卷积神经网络CNN的CNNVariantTrain(有预训练的模型1D和2D),VariantRecalibrator、ApplyVQSR等可以用已知的人类变异数据集作为训练集,检测得到的SNP和INDEL的准确性(官方推荐用于人类变异过滤的方法,Variant Quality Score Recalibration,VQSR)。缺点显而易见,需要已知的真实变异数据集,除人类以外大多数生物都没有这方面的数据集。如果是研究人类基因组的话,可以从GATK官网资源处下载。 2.硬过滤:通过对6个指标的硬性阈值筛选质量合格的SNP和INDEL。 记录下硬过滤的6个指标,有些说明看不懂干脆都放英文了,参考自官网Hard-filtering germline short variants: (1)QualByDepth (QD):This is the variant confidence (from the QUAL field) divided by the unfiltered depth of non-hom-ref samples. 变异置信度,官方建议过滤该值小于2的变异。 (2)FisherStrand (FS):This is the Phred-scaled probability that there is strand bias at the site. (3)StrandOddsRatio (SOR):This is another way to estimate strand bias using a test similar to the symmetric odds ratio test. (4)RMSMappingQuality (MQ):This is the root mean square mapping quality over all the reads at the site.比对reads质量的平方根。 (5)MappingQualityRankSumTest (MQRankSum):This is the u-based z-approximation from the Rank Sum Test for mapping qualities. (6)ReadPosRankSumTest (ReadPosRankSum):This is the u-based z-approximation from the Rank Sum Test for site position within reads. 看不懂没关系,官方给出了6个硬过滤指标在SNP和INDEL中的阈值设置,详情可以看Hard-filtering germline short variants – GATK (broadinstitute.org)。以下例子均以官方的硬过滤指标为准,感兴趣可以去官网看各个参数的作用或者自己微调。 SelectVariants工具用于从vcf文件中提取SNP和INDEL信息,VariantFiltration工具用于硬过滤筛选: 123456789101112131415161718#!/bin/bashsampleName=$1threads=50VARIANTS=/public/home/wlxie/biosoft/GATK_file/gatk/variants_discover/luobuma/raw_variants.vcf.gz# SNP筛选、过滤和提取gatk SelectVariants -select-type SNP -V $VARIANTS --restrict-alleles-to BIALLELIC -O ${sampleName}_SNP.vcf.gz # BIALLELIC 限制双等位基因,不考虑其他等位多态性gatk VariantFiltration -V ${sampleName}_SNP.vcf.gz --filter-expression "QD < 2.0 || MQ < 40.0 || FS > 60.0 || SOR > 3.0 || MQRankSum < -12.5 || ReadPosRankSum < -8.0" --filter-name "Filter" -O ${sampleName}_SNP.filter.vcf.gzgatk SelectVariants -V ${sampleName}_SNP.filter.vcf.gz --exclude-filtered true -O final.${sampleName}_SNP.vcf.gz # 只显示通过过滤的变异(pass)rm -rf ${sampleName}_SNP.vcf.gz*rm -rf ${sampleName}_SNP.filter.vcf.gz*# INDEL筛选、过滤和提取gatk SelectVariants -select-type INDEL -V $VARIANTS --restrict-alleles-to BIALLELIC -O ${sampleName}_INDEL.vcf.gz # BIALLELIC 限制双等位基因,不考虑其他等位多态性gatk VariantFiltration -V ${sampleName}_INDEL.vcf.gz --filter-expression "QD < 2.0 || FS > 200.0 || SOR > 10.0 || MQRankSum < -12.5 || ReadPosRankSum < -8.0" --filter-name "Filter" -O ${sampleName}_INDEL.filter.vcf.gzgatk SelectVariants -V ${sampleName}_INDEL.filter.vcf.gz --exclude-filtered true -O final.${sampleName}_INDEL.vcf.gz # 只显示通过过滤的变异(pass)rm -rf ${sampleName}_INDEL.vcf.gz*rm -rf ${sampleName}_INDEL.filter.vcf.gz* 得到的final.sampleName_SNP.vcf.gz和final.sampleName_INDEL.vcf.gz为最终的变异集结果文件。 3. 结果文件解读因为结果文件时一个压缩过后的vcf文件,且vcf文件中前面带#部分的注释内容是用不到的,后面每一行代表一个变异位点信息,因此可以直直接统计行数来得到最终的SNP和INDEL的数量。 1zcat final.luobuma_SNP.vcf.gz | grep -v -P "^#" -c 用2.2.1多样本流程的SNP和INDEL结果文件为final.luobuma_SNP.vcf.gz和final.luobuma_INDEL.vcf.gz;用2.2.2单个样本流程的SNP和INDEL结果为final.luobuma_sm_SNP.vcf.gz和final.luobuma_sm_INDEL.vcf.gz。可以看到两者在统计SNP和INDEL数量上的差距非常小,说明这两个流程对单样本来说都是可以用的。 以final.luobuma_SNP.vcf.gz文件进行解读,通过less -S命令一行一条信息查看文件内容: 每列信息如下: 1.CHROM:染色体信息 2.POS:变异所在参考基因组的位置 3.ID:变异的ID,如果有参考变异集,会给出id,否则为.表示新发现的变异 4.REF:变异在参考基因组上的信息,必须为ATCGN五个之一 5.ALT:突变之后的情况,类型同上,.表示缺失 6.QUAL:突变后的质量值,质量值越高越可靠,通常只用pass的数据 7.FILTER:是否通过过滤 8.INFO:每个位点的详细信息(包括硬过滤的指标,详细可以到header里找) 9.FORMAT:格式 10.样本名(实际是前面格式的具体值) 主要解释一下第九列和第十列,就是上图中红色框框起来的部分,两列值是用冒号分隔且一一对应的,需要注意的值是GT: GT:0表示和参考序列一致(REF allele),1表示和样本序列一致(ALT allele),双等位基因只有0和1,0/1和0|1表示杂合,1/1和1|1表示纯和。“|”和“/”区别是前者是phased genotype,就是知道REF/ALT allele是来自于父本还是母本,在这里对我这个植物基因组没有什么意义,全都统计进杂合和纯和SNP个数就行。 AD:REF和ALT allele的覆盖度,在二倍体是是用逗号分割的两个值表示,前一个代表参考基因组的基因型,后者代表样本基因型。 DP:样本中该位点覆盖度,AD两个数字的和。 3.1 杂合率统计分别统计SNP和INDEL文件中杂合单碱基变异的个数: 1234zcat final.luobuma_SNP.vcf.gz | grep -c "0/1"zcat final.luobuma_SNP.vcf.gz | grep -c "0|1"zcat final.luobuma_INDEL.vcf.gz | grep -c "0/1"zcat final.luobuma_INDEL.vcf.gz | grep -c "0|1" 杂合率 = (杂合SNP数 + 杂合INDEL数) / 基因组大小 杂合SNP数(Hetero SNP) 杂合INDEL数(Hetero Indel) 基因组大小(bp) 杂合率 1,233,471 287,207 230,888,863 0.66% 此处计算的杂合率可以和前面做的基因组Survey做个比较,说明基因组Survey的可靠性。 3.2 单碱基准确度计算我们知道测序过程中不可避免地存在错误,三代测序数据单碱基变异的来源,包括真实的单碱基变异和测序错误导致的单碱基变异。当测序错误导致的单碱基变异存在于参考基因组上时,利用二代测序数据进行单碱基变异的检测时,会将其识别为纯合单碱基变异。因此,可以将三代数据组装的最终版基因组作为参考基因组,利用二代数据将纯合子单碱基变异率作为组装结果的错误率,即: 组装结果的准确率 = 1 - 纯合子单碱基变异率 1234zcat final.luobuma_SNP.vcf.gz | grep -c "1/1"zcat final.luobuma_SNP.vcf.gz | grep -c "1|1"zcat final.luobuma_INDEL.vcf.gz | grep -c "1/1"zcat final.luobuma_INDEL.vcf.gz | grep -c "1|1" 纯和SNP数(Homo SNP) 纯和INDEL数(Homo Indel) 基因组大小(bp) 纯和子单碱基变异率 组装结果准确率 4,176 8,580 230,888,863 0.005525% 99.994475% 这种单碱基准确度的计算结果也可以作为基因组组装质量的评估指标之一,即序列一致性评估——利用高质量的二代测序数据来评估三代测序数据组装结果在单碱基水平上的准确性。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"GATK","slug":"GATK","permalink":"http://www.shelven.com/tags/GATK/"}]},{"title":"0基础学习基因组三代测序组装(8)——基因组组装质量评估(QUAST)","slug":"0基础学习基因组三代测序组装(8)——基因组组装质量评估(QUAST)","date":"2023-03-02T15:24:41.000Z","updated":"2023-03-04T02:58:11.000Z","comments":true,"path":"2023/03/02/a.html","link":"","permalink":"http://www.shelven.com/2023/03/02/a.html","excerpt":"接上一篇博客,这一篇博客继续介绍一个常用的评估基因组组装质量的软件——QUAST","text":"接上一篇博客,这一篇博客继续介绍一个常用的评估基因组组装质量的软件——QUAST 1. QUAST介绍QUAST(Quality Assessment Tool for Genome Assemblies)是一个比较综合的评估基因组组装质量的软件,主要包括四种分析工具: QUAST:常规基因组组装质量评估 MetaQUAST:宏基因组(元基因组)组装质量评估 QUAST-LG:大型基因组组装质量评估 Icarus:Contig比对可视化工具(类似IGV浏览器的感觉) QUAST用到的软件如下(参考自国家微生物科学数据中心): 序列比对:Minimap2 基因和功能:GeneMarkS、GeneMark-ES、GlimmerHMM、Barrnap和BUSCO 查找结构变异:BWA、Sambamba 覆盖度计算:bedtools MetaQUAST:MetaGeneMark、Krona tools、BLAST和SILVA数据库 QUAST-LG:KMC和Red 这个软件优点是可以使用参考基因组或者无参考基因组情况对组装的基因组进行评估,可以快速进行大批量的基因组组装质量比较,最终的结果有图表、excel和latex等多种表现形式,也有个可以交互的网页结果,非常直观和方便。 2. 安装如果从官网下载的话,需要安装非常多的依赖软件,好消息是:可以conda安装 12345conda install -c bioconda quastquast-download-busco quast-download-gridss # 检测基因组重排的软件quast-download-silva # 著名的16s数据库,提供最新的核糖体大小亚基rRNA注释信息 截至2023年3月2日,最新版本为5.2.0 后续需要安装什么软件都可以conda search一下,能省好多功夫。注意一下conda安装之后会提醒缺两个工具和一个数据库,直接运行命令下载即可。 3. 运行实例以我的植物基因组跑一个常规基因组组装质量评估的例子: 1234#!/bin/bash#SBATCH -n 50quast -t 50 -o quast_baima -1 /public/home/wlxie/clean_data/1.fq -2 /public/home/wlxie/2.fq /public/home/wlxie/NextPolish/baima_rundir/genome.nextpolish.fasta QUAST输入文件只有组装的基因组是必须的,同时也支持三代测序--pacbio、--nanopore数据,也支持二代数据输入。我这里同时输入了二代数据,因此结果文件中有组装基因组的质量评估,也有二代数据回贴组装基因组的分析数据。 4. 结果展示运行结束后的输出日志如下: 最终生成图表结果可以在report.pdf中找到,也可以看report.html,一次运行时常大约为6小时: 左边红框框起来的部分就是二代数据回比基因组的结果,mapping率高于100%说明有多比对,完美比对率(配对reads中两条序列比对上同一个参考基因组序列的比例,Properly Paired)93.45%,覆盖度(coverage)98.63%。这个比对率说明二代测序reads与组装的基因组有较高的一致性(Properly paired 90%以上,coverage 95%以上),可以进行后续的分析。 右边是contig长度累积图,横坐标从左到右contig长度依次减小,曲线越陡表明大片段越长、数量越多,也可以看到基因组组装的连续性良好。 左上角contig的具体数据,以及N50、GC含量可以在transposed_report.txt中查看,同时也提供了latex和excel格式的结果文件,非常贴心~或者可以在basic_stats文件夹中查看相应的pdf图表: Nx图横轴是Nx百分比,比如50就是N50;纵轴是contig长度。这张图也可以反映组装结果的连续性。 最后是icarus网页结果,前面说这个界面有点像IGV。。。总之就是将各个contig从长到短组装情况可视化的工具,可以拖动底下的黄色框左右移动来查看对应的contig情况。 在基因组组装质量评估方面,这个软件就可以一次给出序列一致性、组装完整性和测序覆盖均匀性评估,还是非常方便的~当然,如果你有参考基因组的话,就可以得到更多有效的评估信息。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"QUAST","slug":"QUAST","permalink":"http://www.shelven.com/tags/QUAST/"}]},{"title":"0基础学习基因组三代测序组装(7)——基因组组装质量评估(BUSCO、LAI指数)","slug":"0基础学习基因组三代测序组装(7)——基因组组装质量评估(BUSCO、LAI指数)","date":"2023-03-01T12:39:05.000Z","updated":"2023-03-03T06:30:24.000Z","comments":true,"path":"2023/03/01/a.html","link":"","permalink":"http://www.shelven.com/2023/03/01/a.html","excerpt":"通过前面的纠错和校正步骤,我们得到了组装完成的基因组序列,接下来就是进行基因组的组装质量评估。质量评估的软件和方法比较多,这里分两篇博客记录,本篇主要演示如何用BUSCO和LAI指数评价基因组组装质量。","text":"通过前面的纠错和校正步骤,我们得到了组装完成的基因组序列,接下来就是进行基因组的组装质量评估。质量评估的软件和方法比较多,这里分两篇博客记录,本篇主要演示如何用BUSCO和LAI指数评价基因组组装质量。 复习一下前面说到的contig N50,按照contig从短到长的顺序依次相加,当相加的长度达到Contig总长度的一半,最后一个Contig长度即为contig N50. contig N50是基因组组装质量的第一指标,一般来说越高越好,但是contig N50不能完全代表一个基因组组装质量的高低,比如reads的错误连接也会使contig N50变高。接下来介绍几个现在常用的评估基因组组装质量的软件和方法。 1. 保守型基因评估BUSCO(Benchmarking Universal Single-Copy Orthologs)评估是在基因含量层面上评估基因组完整性。简单来说,通过已有的直系同源数据库进行基因组比对,同源的生物之间有保守基因序列,能比对上的基因数越多说明组装的结果越靠谱。 安装过程: 1234567891011121314151617181920# 1. 源码安装(需要安装前置软件)git clone https://gitlab.com/ezlab/busco.gitcd busco/python setup.py install# 前置软件:https://biopython.org/https://pandas.pydata.org/https://jgi.doe.gov/data-and-tools/software-tools/bbtools/https://ftp.ncbi.nlm.nih.gov/blast/executables/blast+/LATESThttp://bioinf.uni-greifswald.de/augustus/https://github.com/soedinglab/metaeukhttps://github.com/hyattpd/Prodigalhttp://hmmer.org/https://github.com/smirarab/sepp/https://www.r-project.org/# 2. conda安装(推荐)conda install -c conda-forge -c bioconda busco=5.3.2 conda安装可能会比较慢,需要多试几次。实在不行就源码下载编译,不过需要下载非常多的前置软件,不同软件可能会有环境冲突问题、gcc版本问题等等(我花了大半天时间在折腾环境)。安装之后通过busco -h查看是否安装成功,如果提示缺什么软件就用conda补上(我当前环境中没有安装pandas就会有提示)。 通过busco --list-datasets可以查看当前有哪些物种的数据库,我的植物是双子叶龙胆目,这里的数据库只有真双子叶植物(eudicots)分支离的最近,因此选择这个数据库,v5版本所有单拷贝直系同源数据库网址https://busco-data.ezlab.org/v5/data/lineages/ 下载的数据库放在busco_downloads文件夹中,解压即可使用: 12nohup wget https://busco-data.ezlab.org/v5/data/lineages/eudicots_odb10.2020-09-10.tar.gz &tar -zxvf eudicots_odb10.2020-09-10.tar.gz busco的详细参数可以看官网的user guide User guide BUSCO v5.4.4 (ezlab.org) 简单讲一讲格式和能用到的参数: 123456789101112busco -i [SEQUENCE_FILE] -l [LINEAGE] -o [OUTPUT_NAME] -m [MODE] [OTHER OPTIONS]'''主要参数: -i 序列文件位置 -l 下载的同源物种保守基因数据库位置 -o 输出文件名 -m 模式,分为genome,proteins,transcriptome三种 其他参数: --cpu 设置cpu数量 --download 在线下载数据库,根据分类有"all"、"prokaryota"、"eukaryota"和"virus" (不推荐,速度慢) --offline 离线模式,不会更新数据库''' 以下是我跑的程序,大约用了1个小时: 1busco -i /public/home/wlxie/NextPolish/01_rundir/genome.nextpolish.fasta -l /public/home/wlxie/busco_soft/busco/test_data/eukaryota/busco_downloads/lineages/eudicots_odb10 -o baima -m genome --cpu 8 --offline 截至2023/02/28,真双子叶植物库有2326个保守BUSCO基因序列,比对结果文件在short_summary.specific.xxx.xxx.txt中,如下: Complete BUSCOs (C) 多少个基因完全比对上BUSCOs Complete and single-copy BUSCOs (S) 多少个基因比对上单拷贝的BUSCOs Complete and duplicated BUSCOs (D) 多少个基因比对上多拷贝的BUSCOs Fragmented BUSCOs (F) 多少个基因部分比对上BUSCOs,可能基因只是部分存在 Missing BUSCOs (M) 多少个基因没有比对上BUSCOs,可能这些直系同源基因是缺失的 从上面的数据看,组装结果还是不错的。从中也可以看到BUSCO运行的两个步骤:用metaeuk进行基因预测(真核生物可以用tBLASTn与对应的BUSCO数据库序列进行比对从而确定候选区域,然后使用 Augustus 软件进行基因结构预测,两个软件可以替代metaeuk,详细参数见官网),以及HMMER进行同源基因的比对,从而评估基因组组装的完整性。 官方还提供了相应的python程序绘制结果图(调用了R包ggplot2),先将BUSCO结果文件放到新建的文件夹,运行相应的py程序,指定工作目录即可: 12345mkdir summariescp baima/short_summary.specific.eudicots_odb10.baima.txt summariesgenerate_plot.py -wd summaries 结果图如下: 当然,有结果数据就可以自己做更好看的图了,不一定要用官方的。 2. 长末端重复序列评估2018年发表在Nucleic Acids Research上的一篇文章Assessing genome assembly quality using the LTR Assembly Index (LAI),研究者提出了一种对长末端重复序列(long terminal repeats,LTRs)评估从而评价基因组完整度的方法,并且开发了对应的分析工具LTR_retriever 具体的LTR注释我会在后续的基因组注释笔记中更新,这里暂时跳过原理和背景部分,介绍下文章中提出的评估核心——LAI指数(LTR Assembly Index,LAI),也就是长末端重复序列组装指数。raw LAI = (完整LTR-RTs长度/总LTR长度)*100,修正后,LAI = raw LAI + 2.8138 × (94 – 整个基因组LTR identity)。 以下是一个完整的LTR-RTs的结构示意图: 文章还阐明LAI独立于基因组大小、LTR-RT含量以及基因空间评估指标(如BUSCO和CEGMA)等参数,可以用于鉴定低质量的基因组区域。使用这个指标要求**基因组中完整的LTR-RTs应至少占基因组0.1%且总LTR-RTs长度至少占5%**。 文章最后给出了LAI评价基因组完整度的三个指标: 分类 LAI 举例 Draft 0 ≤ LAI < 10 Apple (v1.0), Cacao (v1.0) Reference 10 ≤ LAI < 20 Arabidopsis (TAIR10), Grape (12X) Gold 20 ≤ LAI Rice (MSUv7), Maize (B73 v4) 2.1 LTR序列预测LTR_retriever需要以LTR_finder和/或ltrharvest的LTR预测结果文件为输入,也可以整合两个软件的预测结果作为输入(或者其他符合格式的LTR结果文件),因此需要先安装并运行以上软件,我这里以文章中提到的软件和参数运行。 12345# LTR_finder、ltrharvest和LTR_retriever安装(ltrharvest是genometools软件的一部分)conda install -c bioconda ltr_finderconda install -c bioconda genometools-genometoolsconda install -c bioconda ltr_retriever LTR_finder预测LTR序列(参数均由作者给出,只有文件是自己的): 1234#!/bin/bash#SBATCH -n 10ltr_finder -D 15000 -d 1000 -L 7000 -l 100 -p 20 -C -M 0.85 /public/home/wlxie/NextPolish/01_rundir/genome.nextpolish.fasta > baima_ltrfinder.scn 参数解释: -D NUM Max distance between 5’&3’LTR, default is 20000 # 5’和3’LTR之间的最大距离 -d NUM Min distance between 5’&3’LTR, default is 1000 -L NUM Max length of 5’&3’LTR, default is 3500 # 5’和3’LTR最大长度 -l NUM Min length of 5’&3’LTR, default is 100 -p NUM min length of exact match pair, default is 20 # 完全匹配最小长度 -C detect Centriole, delete highly repeat regions # 检测中心粒,删除高度重复区域 -M NUM min LTR similarity threshold, default is 0.00, [0,1] #最小LTR相似度 ltrharvest预测LTR序列(ltrharvest参数均由作者给出,只有文件是自己的): 12345678#!/bin/bash#SBATCH -n 10mkdir indexgt suffixerator -db /public/home/wlxie/NextPolish/01_rundir/genome.nextpolish.fasta -indexname index/baima -tis -suf -lcp -des -ssp -sds -dnagt ltrharvest -index index/baima -minlenltr 100 -maxlenltr 7000 -mintsd 4 -maxtsd 6 -motif TGCA -motifmis 1 -similar 85 -vic 10 -seed 20 -seqids yes > baima_ltrharvest.scn 这里要注意下要先使用genome tools里的suffixerator创建基因组索引文件,然后才可以使用ltrharvest进行LTR预测。 创建基因组索引的参数不做解释了,可以 gt suffixerator -help 查看。稍微记录下ltrharvest参数: -minlenltr specify minimum length for each LTR,default: 100 -mintsd specify minimum length for each TSD,default: 4 -motif specify 2 nucleotides startmotif + 2 nucleotides endmotif: **** -motifmis specify maximum number of mismatches in motif [0,3],default: 4 -similar specify similaritythreshold in range [1..100%],default: 85.00 -vic specify the number of nucleotides (to the left and to the right) that will be searched for TSDs and/or motifs around 5’ and 3’boundary of predicted LTR retrotransposons, default: 60 -seed specify minimum seed length for exact repeats,default: 30 -seqids use sequence descriptions instead of sequence numbers in GFF3 output,default: no 以上两个软件以同样的LTR最小相似度0.85预测LTR,得到两个结果文件baima_ltrfinder.scn和baima_ltrharvest.scn。 2.2 LAI指数计算用上一步的输出的两个结果文件,运行LTR_retriever鉴定LTR和计算LAI指数。 1234#!/bin/bash#SBATCH -n 20LTR_retriever -genome /public/home/wlxie/NextPolish/01_rundir/genome.nextpolish.fasta -inharvest baima_ltrharvest.scn -infinder baima_ltrfinder.scn -threads 20 这一步运行结果如下: 其他文件可以后续做LTR分析用到,这里我们只要看最后一个LAI的计算结果文件: 可以看到这个结果文件中包含了整个genome和各个contig的raw LAI和LAI指数,这里就只看整个genome的LAI指数15.37,根据上面文章作者提到的分类,属于Reference级别,也就是说可以认为达到参考基因组级别。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"BUSCO","slug":"BUSCO","permalink":"http://www.shelven.com/tags/BUSCO/"},{"name":"LAI","slug":"LAI","permalink":"http://www.shelven.com/tags/LAI/"}]},{"title":"0基础学习基因组三代测序组装(6)——基因组polish","slug":"0基础学习基因组三代测序组装(6)——基因组polish","date":"2023-02-27T03:02:16.000Z","updated":"2023-02-27T03:05:01.000Z","comments":true,"path":"2023/02/27/a.html","link":"","permalink":"http://www.shelven.com/2023/02/27/a.html","excerpt":"三代基因组de novo组装后得到一系列contig,由于三代测序的错误率较高,我们需要对组装结果进行打磨(以下均用polish表示)以提高基因组的拼接指标如Contig N50,Scaffold N50。","text":"三代基因组de novo组装后得到一系列contig,由于三代测序的错误率较高,我们需要对组装结果进行打磨(以下均用polish表示)以提高基因组的拼接指标如Contig N50,Scaffold N50。 常用软件主要有Pilon、Racon,针对PacBio的有Quiver & Arrow,针对Nanopore的有NanoPolish,以及武汉希望组为NextDenovo配套开发的NextPolish等等。要注意下先进行三代测序数据矫正,再进行二代测序数据矫正,顺序不能反,因为三代数据读长长准确率低,二代读长短准确率高,利用二代测序测序数据对三代测序数据进行纠错可以将三代测序错误率降低到二代测序的水平。如果不先进行三代序列纠错,由于基因组上存在过高错误率,导致二代序列的错误比对,影响最终polish效果。 这里以前面用NextDenovo组装的植物三代基因组为例,介绍下Racon和NextPolish用法。 1. Raconracon的基本用法如下 1racon [options ...] <sequences> <overlaps> <target sequences> 需要用到三种输入文件:sequences是指用来纠错的三代基因组测序数据(后面以原始数据称呼);target sequences指需要校正的组装后的基因组数据(后面以组装基因组称呼);overlaps指回比到组装基因组的原始数据文件,其中包含了所有的overlaps,其文件格式为MHAP/PAF/SAM三种之一。 因此在使用Racon之前需要使用其他比对工具将三代数据回贴到组装基因组上,在转录组分析笔记中有介绍过相关软件,我这里用minimap2进行比对,这是专门针对三代测序数据开发的比对工具,运行速度较快。 1minimap2 -a -t 20 <target sequences> <sequences> > minimap_1.sam -a表示结果为sam格式,<target sequences>处传入组装基因组的绝对路径,<sequences>处传入原始数据的绝对路径,比对结果的sam文件命名为minimap_1.sam 1racon -t 50 <sequences> minimap_1.sam <target sequences> > racon_minimap_1.fasta 如上一次循环下来(大约3小时),得到的racon_minimap_1.fasta就是经过一次三代数据校正的组装基因组。 一般要用三代数据polish 2-4次,之后用二代数据继续校正4次左右,可以写脚本循环,需要注意racon因为要一次读入三代原始数据和回比的sam数据,内存需求量非常大,申请的内核数需要自己计算一下,否则会报内存溢出的错误(220Mb的基因组,100X测序深度,申请50个核才能跑动)。 脚本文件racon.sh如下: 1234567#!/bin/bashminimap2 -a -t 50 $1 $2 > minimap_1.samracon -t 50 $2 minimap_1.sam $1 > racon_minimap_1.fastaminimap2 -a -t 50 racon_minimap_1.fasta $2 > minimap_2.samracon -t 50 $2 minimap_2.sam racon_minimap_1.fasta > racon_minimap_2.fasta recon.slurm: 123456789#!/bin/bash#SBATCH -J recon#SBATCH -N 1#SBATCH -n 50#SBATCH -t 7200datebash racon.sh /public/home/wlxie/NextDenovo/03_rundir/03.ctg_graph/nd.asm.fasta /public/home/wlxie/luobuma/luobuma/baima_rawdata/Third_generation_sequencing/clean_filter.fqdate 1sbatch recon.slurm 可以从输出日志中看到,racon运行主要分两步,分别是校准overlap和生成共有序列(也就是去重),在生成共有序列(consensus sequence)之后再进行二代数据的纠错。 这一步的Racon检测出两个contig可能是嵌合体(chimeric),所谓嵌合contig,该contig的某段区域可能可以比对上不同的染色体,或者头尾部分可能分别属于不同的染色体。第一遍racon的时候没有检测到,第二遍racon才出现这个提示,我不确定这两个contig是否真的是嵌合体,最终还是需要Hi-C数据来验证。 我这里两次循环得到polish的结果文件racon_minimap_2.fasta,接下来用NextPolish软件继续用二代数据polish。 2. NextPolishNextPolish是武汉那希望组开发的与NextDenovo配套的基因组polish软件,支持二代短读长、三代长读长和HiFi数据进行纠错。 和之前的NextDenovo操作方法类似,首先需要准备一个input文件,写入二代数据的绝对路径到sgs.fofn: 1realpath ./1.fq ./2.fq > sgs.fofn 从doc文件夹中copy一份配置文件run.cfg,修改参数: 1234567891011121314151617[General]job_type = slurm # local, slurm, sge, pbs, lsf塔大学校集群选择slurmjob_prefix = nextPolishtask = best # all, default, best, 1, 2, 5, 12, 1212… 1,2是两个不同的短读长算法模块,5是长读长算法模块,默认bestrewrite = yesdeltmp = yes # 删除临时结果文件rerun = 3 # 重复执行polish次数parallel_jobs = 20 # 每个job线程数multithread_jobs = 5 # job数genome = /public/home/wlxie/baima_polish/racon_minimap_2.fastagenome_size = autoworkdir = ./01_rundirpolish_options = -p {multithread_jobs}[sgs_option] #optionalsgs_fofn = ./sgs.fofn # 输入文件位置(一行一条)sgs_options = -max_depth 100 -bwa # 使用bwa进行比对 长reads和HiFi的两段配置信息删除,只留下短读长sgs_options。 这个软件的优点是速度快(100线程,4次polish,220Mb的基因组,72G的二代数据量仅仅用了8小时),而且只需要提供配置和输入文件就可以到polish结束出结果,经过4次Polish结果的迭代,最终结果如下: Contig N50比一开始NextDenovo组装结果大,也就是组装效果更好。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"NextPolish","slug":"NextPolish","permalink":"http://www.shelven.com/tags/NextPolish/"},{"name":"Racon","slug":"Racon","permalink":"http://www.shelven.com/tags/Racon/"}]},{"title":"0基础学习基因组三代测序组装(5)——三代数据组装","slug":"0基础学习基因组三代测序组装(5)——三代数据组装","date":"2023-02-23T14:22:00.000Z","updated":"2023-02-28T13:07:16.000Z","comments":true,"path":"2023/02/23/a.html","link":"","permalink":"http://www.shelven.com/2023/02/23/a.html","excerpt":"前段时间比较忙,现在继续整理基因组测序组装系列的学习笔记。第四篇笔记写的二代测序基因组组装,主要是演示二代测序数据组装的主流工具SOAPdenovo 2.0是如何应用的。我这里有了二代和三代的测序数据,后续组装还是以三代数据为主,这里就继续记录下几款三代测序数据组装的主流工具和用法。","text":"前段时间比较忙,现在继续整理基因组测序组装系列的学习笔记。第四篇笔记写的二代测序基因组组装,主要是演示二代测序数据组装的主流工具SOAPdenovo 2.0是如何应用的。我这里有了二代和三代的测序数据,后续组装还是以三代数据为主,这里就继续记录下几款三代测序数据组装的主流工具和用法。 现在主流的三代测序公司是Pacbio和Nanopore,两家测序公司测序原理不同,产生的数据类型也有区别。 1. 主流三代测序平台1.1 Pacbio测序平台Pacbio测序平台是单分子实时测序(single molecule real time sequencing,SMRT),原理是当DNA与聚合酶形成的复合物被ZMW(零模波导孔)捕获后,4种不同荧光标记的dNTP随机进入检测区域并与聚合酶结合,与模板匹配的碱基生成化学键并激发荧光,生成化学键激发的荧光存在的时间远远长于其他碱基被激发荧光的时间,从而实现单碱基的实时检测。 现在Pacbio有两种测序模式,一种是CLR测序模式(超长测序模式),产生数据基于单循环测序结果;一种是HiFi测序模式,也就是高保真测序模式,产生数据基于滚环一致序列(Circular Consensus Sequencing ,CCS)。具体原理就不说了,两种测序模式中HiFi数据相对来说准确率会高一些,所以不同软件对两种测序模式的数据也会有不同的处理。 1.2 Nanopore测序平台Nanopore测序平台前面第一篇博客介绍过了,可以点击这里。需要了解ONT测序平台测序产生的原始数据是电信号,经过basecalling之后才可以转化成我们要的测序数据。 2. 三代基因组测序组装软件2.1 NextDenovoNextDenovo是武汉希望组开发的集校正、比对和组装一体的,基于字符串图(string graph-based)的三代测序基因组组装软件。它的实现过程和另一款经典的三代基因组组装软件Canu类似,经过长读长数据的纠错校正后再进行组装。 官网上介绍原来可以对CLR、HiFi和ONT数据都可以组装,HiFi数据可以跳过数据的自我纠错过程,如今HiFi数据被划掉了,也许已经不再适用,但是对Pabio的CLR和Nanopore的ONT测序数据仍有较好的组装效果,其介绍是组装的准确率有98%-99.8%。 NextDenovo主要有两个核心模块 NextCorrect和NextGraph。NextCorrect用于原始数据纠错,NextGraph用于纠错后数据的组装。据作者介绍,与其他工具相比,NextDenovo在装配一致性和单碱基装配精度方面表现出较高的水平。我个人用起来是觉得这个软件运行时间相对Canu较短,需要的算力资源较小,可以很快地组装出结果(后面可以进行比较)。 安装并测试通过后,我们就可以开始使用这个工具了。 首先准备input文件,将前面质控后的三代测序数据的绝对路径写在input.fofn文件里: 1/public/home/wlxie/luobuma/luobuma/baima_rawdata/Third_generation_sequencing/clean_filter.fq 接下来也是最重要的,修改配置文件: 123456789101112131415161718192021222324[General]job_type = slurm # local, slurm, sge, pbs, lsf塔大学校集群选择slurmjob_prefix = nextDenovotask = all # all, correct, assemble选择只进行correct还是只进行assemble,或者两者都进行,基因组小的话可以直接allrewrite = yes # yes/no覆写deltmp = yes parallel_jobs = 20 # number of tasks used to run in parallel线程数,咱学校的集群20勉强够input_type = raw # raw, corrected输入的数据情况read_type = ont # clr, ont, hifi数据类型input_fofn = input.fofn # 输入数据的位置信息workdir = 03_rundir # 输出的文件夹名字[correct_option]read_cutoff = 1k # 进行correct的时候截取的最小readgenome_size = 230m # estimated genome size预估的基因组大小sort_options = -m 20g -t 15minimap2_options_raw = -t 8pa_correction = 3 # number of corrected tasks used to run in parallel, each corrected task requires ~TOTAL_INPUT_BASES/4 bytes of memory usage.correction_options = -p 15 -dbuf # 非常重要!-dbuf让每一步作业释放内存,防止节点卡死![assemble_option]minimap2_options_cns = -t 8 nextgraph_options = -a 1 以上是我的配置文件信息,中文标注的地方都很重要,根据情况修改。其他参数可以用默认。其中预估的基因大小也是很有必要的,前面在做基因组Survey的时候预测过,这里就直接写预测的基因组大小。 其他参数的设定和使用可以参考这篇博客使用NextDenovo组装Nanopore数据,以及官方的参数说明手册NextDenovo Parameter Reference — NextDenovo latest documentation 需要强调一点,correction_options = -p 15 -dbuf这项参数是我在华农的集群平台手册上看到的,之前确实一直会卡死在某一步直到24h后台强杀这个进程,目前未知原因,加上之后运行正常。以我的数据来看,一个200多Mb的植物基因组,测序深度100X左右,一次组装运行结束需要12小时左右,已经非常快了。 最后是运行程序,我写了一个run.slurm文件: 1234#!/bin/bash#SBATCH -n 40./nextDenovo run.cfg 1sbatch run.slurm 基因组比较大的话,建议分步运行,先correct,再assemble。 因为是在集群中运行,所有输出都会在slurm-xxxx.out的文件夹中显示,打开以后可以看到每个时间节点完成了什么任务,当有任务卡住几个小时都没动的时候,就要检查是否是配置文件是否正确。 最后组装的基因组在03.ctg_graph目录下,文件名称nd.asm.fasta。最底下输出了组装结果概况,contig N50为9Mb,总共组装出225Mb的基因组序列,contig总数为59,组装结果还算不错。 2.2 CanuCanu是三代测序数据组装的经典工具,也是主要用于Pacbio和Nanopore公司的测序结果组装。 这个软件在安装过程中有点曲折,从官网下数据包,最后一步编译的过程会报错。目前我暂时没办法解决,但是可以用conda安装(虽然官网不建议这么做)。 1conda install -c conda-forge -c bioconda -c defaults canu 如果报错动态库出问题,可以参考第三篇博客中的方法,寻找根目录下的动态库中是否有对应的版本文件,如果有,直接修改软链接到对应的动态库下。 Canu运行分为三个步骤:纠错(Correct)、修剪(Trim)和组装(Assemble)。可以每一个步骤分开跑,比如纠错修剪之后的数据可以放到别的软件中组装,或者用别的软件纠错之后作为输入到Canu中组装。考虑到塔大集群24小时自动杀程序,保险起见还是三个步骤分开跑比较安全。 下面是官方的帮助文档,写的非常详细: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960usage: canu [-version] [-citation] \\ [-haplotype | -correct | -trim | -assemble | -trim-assemble] \\ [-s <assembly-specifications-file>] \\ -p <assembly-prefix> \\ # 输出文件前缀 -d <assembly-directory> \\ # 输出目录 genomeSize=<number>[g|m|k] \\ # 预测基因组大小 [other-options] \\ [-haplotype{NAME} illumina.fastq.gz] \\ [-corrected] \\ [-trimmed] \\ [-pacbio | -nanopore | -pacbio-hifi] file1 file2 ...example: canu -d run1 -p godzilla genomeSize=1g -nanopore-raw reads/*.fasta.gz To restrict canu to only a specific stage, use: # 描述canu要执行的主程序 -haplotype - generate haplotype-specific reads -correct - generate corrected reads -trim - generate trimmed reads -assemble - generate an assembly -trim-assemble - generate trimmed reads and then assemble them Reads can be either FASTA or FASTQ format, uncompressed, or compressed with gz, bz2 or xz. Reads are specified by the technology they were generated with, and any processing performed. [processing] # 描述reads状态 -corrected -trimmed [technology] # 描述测序平台(数据类型) -pacbio <files> -nanopore <files> -pacbio-hifi <files> Some common options: useGrid=string - Run under grid control (true), locally (false), or set up for grid control but don't submit any jobs (remote) rawErrorRate=fraction-error # 降低这个参数会提高第一步的速度 - The allowed difference in an overlap between two raw uncorrected reads. For lower quality reads, use a higher number. The defaults are 0.300 for PacBio reads and 0.500 for Nanopore reads. correctedErrorRate=fraction-error # 降低这个参数可以提高组装效率 - The allowed difference in an overlap between two corrected reads. Assemblies of low coverage or data with biological differences will benefit from a slight increase in this. Defaults are 0.045 for PacBio reads and 0.144 for Nanopore reads. gridOptions=string - Pass string to the command used to submit jobs to the grid. Can be used to set maximum run time limits. Should NOT be used to set memory limits; Canu will do that for you. minReadLength=number - Ignore reads shorter than 'number' bases long. Default: 1000. minOverlapLength=number - Ignore read-to-read overlaps shorter than 'number' bases long. Default: 500. A full list of options can be printed with '-options'. All options can be supplied in an optional sepc file with the -s option. # 可以用-s来提供自己修改的参数文件Complete documentation at http://canu.readthedocs.org/en/latest/ 也可以点击这里,进官方手册看原文。下面用我自己的三代数据跑一个案例。 2.2.1 纠错(Correct)因为三代测序数据错误率较高,纠错的步骤是通过序列之间的一致性比较获得高可信的碱基。 创建一个canu的空文件夹,写入以下内容到correct.slurm: 12345#!/bin/bash#SBATCH -N 1#SBATCH -n 40canu -correct -p baima -d baima_nanopore genomeSize=230m -nanopore-raw /public/home/wlxie/luobuma/luobuma/baima_rawdata/Third_generation_sequencing/pass.fq 1sbatch correct.slurm 主要就是注意下参数,-p是输出文件的前缀,-d是输出文件的目录名,需要声明这个数据是什么平台测的,以及数据是什么状态。虽然我这里只申请了40个核,但是canu会自动提交作业直到你能申请的核数上限……在塔大集群我的权限是200个核,通过scontrol show job 可以查到,我这边一次性提交了136个作业,排队100多个任务,占用192个核….. 纠错、修整和组装每一个步骤会依次进行以下各个阶段,需要的内存和核数挺高的,所以推荐在集群中运行。 如果运行时间很长,建议到设置的输出文件夹目录下查看canu.out文件,详细记载了正在执行哪一步以及花了多少时间。如果不确定程序是否卡死,直接通过scontrol show job命令查看状态为RUNNING的作业,进入作业的输出目录,如果文件夹中的内容一直到最近的时间点都有更新,则可以放心地继续运行。 仅仅这一个步骤花了30个小时。并且这一步会将100X的测序数据量降到40X(默认,可以调整,见官方readSampleingCoverage参数介绍)。最后生成文件baima.correctedReads.fasta.gz,我为了方便复制到了前一个文件夹,修改文件权限为0755。 2.2.2 修整(trim)修整是在上一步纠错的基础上,再对reads进行修剪,删去可疑区域。 这一步经历的步骤与上一步是一样的,虽然上一步纠错已经将数据量降了一大半,对于我的基因组,这一步依然要跑32小时. 同样在canu文件夹写入如下内容到trim.slurm: 12345#!/bin/bash#SBATCH -n 50#SBATCH -t 7200canu -trim -p baima -d baima_trim genomeSize=230m -corrected -nanopore /public/home/wlxie/canu/baima.correctedReads.fasta.gz 1sbatch trim.slurm 注意修改参数以及reads所处的状态。 在canu.out输出文件中,可以找到trim步骤用了什么参数,处理了哪些类型的reads: 生成的结果文件在baima_trim文件夹中,名称为baima.trimmedReads.fasta.gz,同样修改文件权限,移到前一个文件夹方便操作。 2.2.3 组装(Assemble)经过前两步的数据纠错和修整,这一步才是正式组装基因组。 在canu文件夹写入如下内容到assemble.slurm: 12345#!/bin/bash#SBATCH -n 50#SBATCH -t 10800canu -assemble -p baima -d baima_assemble genomeSize=230m correctedErrorRate=0.144 -trimmed -corrected -nanopore /public/home/wlxie/canu/baima.trimmedReads.fasta.gz 1sbatch assemble.slurm 在官方的介绍中,correctedErrorRate这个参数可以根据前面纠错和修整的reads质量做修改的,默认是Pacbio数据0.045,Nanopore数据0.144,降低这个参数值可以加快组装的效率,但是存在遗漏overlap和组装片段断裂的风险。低于30X测序深度以下的数据,官方建议可以略微提高这个值,对于60X测序深度以上的数据可以略微降低这个值,每次改变1%左右比较合适。 我这里就用默认参数了,组装时间可能会比较长,就将作业的时间调整为7天。 实际运行时间为30小时,最终组装结果如下: 这个工具组装出的结果比预期大很多,前面基因组survey预测的基因组大小为230Mbp,实际通过canu组装出有276Mbp,且contig数明显比NextDenovo多,导致contig N50指标低。我觉得可能是correctedErrorRate这个值比较高,可以适当调低一些,过于严格的纠错标准可能导致组装的contig比较碎。 因为前面的NextDenovo组装的效果已经比较理想,因此这一步我也就不再细调参数了。以NextDenovo组装出的基因组继续后面的分析。以目前我的植物基因组来看,用NextDenovo组装三代基因组的效率和质量都比Canu高。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"NextDenovo","slug":"NextDenovo","permalink":"http://www.shelven.com/tags/NextDenovo/"},{"name":"Canu","slug":"Canu","permalink":"http://www.shelven.com/tags/Canu/"}]},{"title":"解决github DNS污染的三种方法","slug":"解决github-DNS污染的三种方法","date":"2023-02-21T13:47:13.000Z","updated":"2023-02-21T13:49:36.000Z","comments":true,"path":"2023/02/21/a.html","link":"","permalink":"http://www.shelven.com/2023/02/21/a.html","excerpt":"最近因为需要安装各种生信软件,用github比较多,每次登github都要翻墙很是麻烦。索性花了点时间研究了一下,下面分别列举三种情况下的解决github网站登录的问题。","text":"最近因为需要安装各种生信软件,用github比较多,每次登github都要翻墙很是麻烦。索性花了点时间研究了一下,下面分别列举三种情况下的解决github网站登录的问题。 知其然知其所以然,首先我们需要明白为什么github在国内经常无法登录。我们在浏览器地址栏输入网址以后,域名服务器会对输入的域名进行解析(DNS解析),解析成为计算机之间可以识别的ip地址。然而因为一些众所周知的因素,很多国外的网站在国内是无法直接访问的,其中一个限制方法就是DNS污染,将域名服务器中缓存的域名指向不正确的ip地址。这种限制手段就和你公司电脑会限制你浏览一些网站是一样的。 因此要浏览这些被DNS污染的网站,我们需要跳过受污染的局域域名服务器,常用的方法就是代理服务器和VPN(VPN就是一个典型的正向代理),通过更远的服务器转发我们的http请求,在经过未污染的域名服务器解析之后,返回我们想要的网页内容。当然,如果仅仅只是用翻墙的方法的话,本篇博客这里可以结束了,下面通过三种情况分别讲一下如何通过修改host等其他方法访问github。 1. Windows系统修改host首先注意一点,如果你正在用翻墙软件或者VPN等,需要先将所有代理都关闭(防止全局代理导致设置的host失效)。 host文件是一个没有扩展名的系统文件,可以用notepad++打开编辑,其本质就是就是将访问的域名和ip地址建立关联。当浏览器中输入网址时,系统会首先从host文件中找到域名对应的ip地址,如果没有找到才会发送给域名解析器。所以我们只需要找到github相关域名对应的正确的ip地址,即可正常访问github(其他受DNS污染的网址同理)。 打开网址 https://www.ipaddress.com/ 主要查找以下github相关域名的ip地址,有多少个记录多少个 123456github.com # 主站nodeload.github.com api.github.com # APIcodeload.github.comgithub.global.ssl.Fastly.net # git clone速度相关assets-cdn.github.com # 静态资源相关 windows系统中host文件位置 C:\\WINDOWS\\System32\\drivers\\etc\\hosts notepad++打开host文件,最后加入如下查到的ip地址,保存 12345678910111213141516# domain: github.com# 更新时间 2023年2月21日140.82.113.3 github.com140.82.114.10 nodeload.github.com140.82.112.6 api.github.com140.82.114.10 codeload.github.com151.101.1.194 github.global.ssl.Fastly.net151.101.65.194 github.global.ssl.Fastly.net151.101.129.194 github.global.ssl.Fastly.net151.101.193.194 github.global.ssl.Fastly.net185.199.108.153 assets-cdn.github.com185.199.110.153 assets-cdn.github.com185.199.111.153 assets-cdn.github.com 最后一步打开cmd命令行,刷新缓存就可以正常登录github了 ipconfig /flushdns 2. Linux系统修改host方法步骤同上,需要注意linux系统修改host需要root权限! linux系统host文件路径 /etc/hosts 3. 无root权限的linux系统访问github有些时候我们会在集群中安装软件,这个时候是没有root权限的,无法通过修改host的方法直接访问,因此也无法用git clone的方法克隆仓库(会报错,错误代码443)。 今天碰到这个问题,找贺师兄聊了聊才发现可以找镜像资源曲线救国……突然打开了新世界的大门hhhh 真的解决了困扰我很久的疑惑,按照往常我只能翻墙下载源代码,再解压后传回服务器,一来一回校园网的速度要传很久很久…… 具体做法是先下载油猴(Tampermonkey)插件,这是个非常有名的脚本管理器,下载安装方式就不说了,网上一大把,自己搜下github官方也非常简单。Tampermonkey/tampermonkey 然后是安装github增强加速插件,插件地址 https://greasyfork.org/zh-CN/scripts/412245-github-%E5%A2%9E%E5%BC%BA-%E9%AB%98%E9%80%9F%E4%B8%8B%E8%BD%BD 我因为安装过了,这里就只演示一下: 安装之后再回到原来github页面,点击code按钮,就可以看到原来只有一个git clone地址,现在有好几个地址给出来了,在集群里随便git clone选择其他地址,就可以在成功下载啦。 这个javascript脚本感兴趣的话可以在油猴中看看,本质上也是个CDN加速和代理,只不过用的都是公益资源,速度也相当不错了。 4. 写在最后前面两个方法改host并不是一劳永逸的,隔一段时间github的ip地址就会更新(不定,可能几天,可能几周)。这种纯手动更新的方法仍然是不够智能不够优雅的,有能力的话以后写一个自动更新的脚本(间隔一段时间自动查找相关github域名的ip地址,自动更新到host文件中)。 目前为止,还是代理最为省心。","categories":[{"name":"网络相关","slug":"网络相关","permalink":"http://www.shelven.com/categories/%E7%BD%91%E7%BB%9C%E7%9B%B8%E5%85%B3/"}],"tags":[{"name":"github","slug":"github","permalink":"http://www.shelven.com/tags/github/"}]},{"title":"校园网代理服务器的搭建","slug":"校园网代理服务器的搭建","date":"2023-02-09T08:55:45.000Z","updated":"2023-02-09T08:58:49.000Z","comments":true,"path":"2023/02/09/a.html","link":"","permalink":"http://www.shelven.com/2023/02/09/a.html","excerpt":"以前的一篇博客讲过如何搭建反向代理服务器,从而实现在校外登录校内集群,详情点击这里。本篇博客主要记录下如果想要在校外登录学校教务平台、登录学校购买的数字资源库应该如何实现。","text":"以前的一篇博客讲过如何搭建反向代理服务器,从而实现在校外登录校内集群,详情点击这里。本篇博客主要记录下如果想要在校外登录学校教务平台、登录学校购买的数字资源库应该如何实现。 1. 实现思路首先要更正一下前一篇博客的错误之处,学校集群是有公网ip的,只是限制了ip访问。 使用的工具仍然是frp,需要的设备是一台在校内24h不断电的服务器(本篇博客中的校内集群),以及一台有公网ip的校外服务器(本篇博客中是我的轻量云服务器)。 上篇内网穿透博客记录的是典型的反向代理过程,将校内集群ssh登录的端口映射到校外服务器的其他端口,通过访问该端口登录校内集群,整个代理过程客户端无法得知服务端的真实ip和端口,可以做到隐藏服务端真实信息、确保服务端安全。 而我们这里想要登录校园内的其他网站、使用校园网ip登录知网万方等数据库的话,就需要用校内集群转发我们的http请求,将请求返回的结果通过校外服务器中转后返回给我们,按照我的理解,这是一个正向代理+反向代理结合的过程。 反向代理:隐藏了服务端,我并不知道我访问的实际是校内集群(输入的是校外服务器的ip地址和端口)。 正向代理:隐藏了客户端,集群将http请求转发到校内网站和学校买的数据库网站(后者并不知道发出请求的实际是校外服务器)。 校内集群在整个过程中信息完全被隐藏,校外服务器起到中转的作用,最后将http请求返回的结果传递给我们。 2. frp配置2.1 service端配置frps安装在校外服务器上,frps.ini配置文件完全不用修改,这里就顺便展示一下 123456789101112131415[common]bind_port = 7000 # frp监听的端口,默认7000,可改bind_udp_port = 7400 # UDP通讯端口,可不设置,用于点对点穿透token = xxxxxxxx # 安全考虑需要设置口令,client端需要用到dashboard_port = 7500 # frp管理端口,可改dashboard_user = xxxx # 管理端口认证的用户名,用于身份识别,自己设置dashboard_pwd = xxxx # 管理端口认证的密码,用于身份识别,自己设置enable_prometheus = truesubdomain_host = xxx.xxx.xxx # 设置子域名,主要方便登录管理界面。不用ip地址,用域名+端口的方式直接访问log_file = /usr/local/frp/frps.log # frp日志配置,这里是记录3天的日志信息log_level = infolog_max_days = 3 2.2 client端配置frpc安装在校内集群,frpc.ini配置文件修改如下 123456789[common]server_addr = xxx.xxx.xxx.xxx # 填写你的service端服务器公网ip,这里我写我的云服务器ipserver_port = 7000 # 前面设置的frp监听端口,需要保持一致token = xxxxxxxxx # 前面设置的口令[http_proxy] # 这里只演示http代理,有ssh需求的自行加入,其他参考frpc_full.initype = tcpremote_port = xxxx # 映射的service端服务器的端口,自己定义plugin = http_proxy # http代理插件(frpc自带) 2.3 开放端口,开启frp服务service端(也就是校外服务器)开放上一步client端设置的服务器端口,重载防火墙。 分别在service端和client端后台不挂起运行frps和frpc(对应文件夹中运行) 12nohup ./frps -c frps.ini &nohup ./frpc -c frpc.ini & service端可以查看运行日志frps.log文件,端口监听成功即可;client端可以查看nohup文件的运行结果(最好定时清一下,否则这个文件会很大)。 如果想要停止frp服务,查看任意service端或者client端的frp程序进程,结束即可 12ps -aux | grep frp # 查看任务进程号kill -9 进程号 # 结束任务进程 3. 代理实现按照上面步骤搭建好代理服务器,我们只需要在电脑上设置代理地址和端口,就可以在电脑上用外网访问校园内网和数据库网站了。 上面的图片我就不放真实信息了,根据图片意思设置即可,一般来说教程到这里就可以结束了,但是仅仅如此还不够优雅。 如果不对地址进行限制,那么所有http访问都将通过代理进行,即全局代理,一般情况下我们是不希望如此的。举个例子,如果访问的是localhost,我们一般是用直连(Direct);访问限制ip的特定的网站走代理服务器(Proxy);访问不限制ip的网站仍然用直连,因为直连速度最快,只受你电脑带宽限制。 如果访问所有网站都用代理的话,比如我的小破服务器就1M带宽,速度就很感人了…… 简单讲一讲三种方法优雅地实现校园网代理 3.1 使用设置脚本没错就是手动设置代理上面那个选项,这个脚本是以**.PAC为扩展名的JavaScript脚本**,决定http请求是通过直连目标还是通过代理的方式连接。 pac文件中使用的JavaScript 函数可以在官方查到用法,这里做个示例: 12345678910111213141516171819function FindProxyForURL(url, host) { //设置代理池 var proxy1 = "PROXY xxx.xxx.xxx.xxx:xxxx"; var proxy2 = "PROXY xxx.xxx.xxx.xxx:xxxx"; //本地地址直连 if (isPlainHostName(host)) { return "DIRECT"; } // 代理1 if (shExpMatch(url, "*.cnki.com/*")) { return proxy1; } // 代理2 if (shExpMatch(url, "*.wanfangdata.com.cn/*")) { return proxy2; } return "DIRECT";} 将上面的文件保存为.pac格式文件,代理池部分填写前面做的代理服务器ip和端口(当然我只有一个代理服务器,根据需要自己改),需要代理的网址这里可以用shExpMatch函数进行正则匹配,完成后再设置自动设置代理部分。打开自动检测设置和使用设置脚本,将.pac文件的地址(可以是本地地址或者放在你自己的服务器上,能访问到就行)贴到脚本地址栏。 接下来就可以愉快地访问学校购买的数字资源啦并且上其他网站因为是直连网速也不会变慢 3.2 使用其他代理软件代理软件种类繁多,相比直接设置脚本,代理软件往往还提供更多更直观的方式控制http代理方式,这里就简单介绍个软件Proxifier 设置方法与手动设置大同小异(手动设置的代理规则比较反人类),同样是设置代理服务器ip,端口,可以设置不同的协议,还可以启用验证保证安全。 设置代理规则 代理规则就不一一细说了,这个软件就是比较直观,而且还可以记录代理产生的流量等等。 3.3 做个切换全局代理工具如果是自己用的话,使用代理脚本是最简单最优雅又不费事的方法。 如果要给同一个实验室其他人分享,又怕别人电脑不安全(直接分享脚本会导致源码泄露,万一别人电脑被黑了自己服务器的信息就被暴露了),就可以做一个代理工具,别人用得着的时候开启,用不着的时候就关闭(全局代理的重要性,就算忘记关闭代理,看到别的网页速度变慢了,也就会想起来去关闭代理了),可以一定程度上保护自己服务器的安全,又简化别人设置代理的步骤,一举两得哈哈~ 实现方式就是做一个批处理,然后将bat转化为exe即可 1234567891011121314151617181920212223242526272829303132333435363738394041424344@echo offfor /f "tokens=1,2,* " %%i in ('REG QUERY "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings" /v ProxyEnable ^| find /i "ProxyEnable"') do (set /A ProxyEnableValue=%%k)if %ProxyEnableValue% equ 0 ( echo 正在开启知网代理,请稍候... ping -n 2 127.0.0.1>nul echo= echo 获取注册表中代理启用状态...... reg add "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings" /v ProxyEnable /t REG_DWORD /d 1 /f ping -n 2 127.0.0.1>nul echo= echo 设置代理服务器...... reg add "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings" /v ProxyServer /d "xxx.xxx.xxx.xxx:xxxx" /f ping -n 2 127.0.0.1>nul echo= echo 代理已开启,请阅读弹窗内容并按确认键关闭本窗口... echo= echo 知网一键代理工具 Version 1.0 echo 版权所有 塔里木大学研发中心405. 保留所有权利。 echo 该工具由405实验室内部开发,仅供本实验室人员使用,切勿外传。 echo msgbox"代理开启期间网速会变慢,使用完毕后请再次点击该工具结束代理!",0,"提示"> %tmp%\\\\tmp.vbs cscript /nologo %tmp%\\\\tmp.vbs del %tmp%\\\\tmp.vbs) else if %ProxyEnableValue% equ 1 ( echo 正在关闭知网代理,请稍候... ping -n 2 127.0.0.1>nul echo= echo 获取注册表中代理启用状态...... reg add "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings" /v ProxyEnable /t REG_DWORD /d 0 /f ping -n 2 127.0.0.1>nul echo= echo 清除代理服务器设置...... reg add "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings" /v ProxyServer /d "" /f ping -n 2 127.0.0.1>nul echo= echo 代理已关闭,请按确认键退出本窗口... echo= echo 知网一键代理工具 Version 1.0 echo 版权所有 塔里木大学研发中心405. 保留所有权利。 echo 该工具由405实验室内部开发,仅供本实验室人员使用,切勿外传。 echo msgbox"再见。",0,"提示"> %tmp%\\\\tmp.vbs cscript /nologo %tmp%\\\\tmp.vbs del %tmp%\\\\tmp.vbs) 上面的内容文件保存为.bat后缀文件(有中文的话,保存时编码格式要改为ANSI),只需修改xxx.xxx.xxx.xxx:xxxx部分为你前面设置的代理服务器ip和端口即可。 原理就是获取注册表中代理服务的开启状态,转化为另一种状态, ping -n 2 127.0.0.1这个只是为了使用者有参与感……延迟两秒进行下一步处理hhhhhh 最后网上找个图,转成icon格式作为图标,然后将bat批处理格式文件转化成exe可执行程序文件即可,我这里用的Bat_To_Exe_Converter这个软件,转成exe源码不容易被泄露,而且怎么说呢看上去给人感觉也稍微正式一点(bushi)。 大概就是这样,每双击一次应用程序,切换全局代理的状态为开或者关 4. 注意浏览器是会缓存ip信息的,无论使用何种方式开启代理,最好都先关闭浏览器之后重新打开。","categories":[{"name":"网络相关","slug":"网络相关","permalink":"http://www.shelven.com/categories/%E7%BD%91%E7%BB%9C%E7%9B%B8%E5%85%B3/"}],"tags":[{"name":"内网穿透","slug":"内网穿透","permalink":"http://www.shelven.com/tags/%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F/"},{"name":"代理服务器","slug":"代理服务器","permalink":"http://www.shelven.com/tags/%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8/"}]},{"title":"基于requests和Xpath改进微信公众号爬虫","slug":"基于requests和Xpath改进微信公众号爬虫","date":"2022-12-18T15:00:18.000Z","updated":"2022-12-18T15:03:38.000Z","comments":true,"path":"2022/12/18/a.html","link":"","permalink":"http://www.shelven.com/2022/12/18/a.html","excerpt":"前面一篇博客讲了requests、Xpath和selenium的用法,最后用selenium模拟浏览器对搜狗微信文章做了自动化爬取。从搜狗微信网页爬取的公众号文章其实是不全的,不能保证公众号的所有文章都被搜狗收录,且selenium爬取速度相对较慢(但是对动态页面爬取很有用),因此可以选择另一种方式——直接从微信公众号后台进行爬取。","text":"前面一篇博客讲了requests、Xpath和selenium的用法,最后用selenium模拟浏览器对搜狗微信文章做了自动化爬取。从搜狗微信网页爬取的公众号文章其实是不全的,不能保证公众号的所有文章都被搜狗收录,且selenium爬取速度相对较慢(但是对动态页面爬取很有用),因此可以选择另一种方式——直接从微信公众号后台进行爬取。 这两天改了下代码,就讲一讲从微信公众号后台爬文章的思路。 1. 准备工作首先是申请微信公众号,自从2018年微信公众号加强用户管理以后,一个身份证只可以注册一个订阅号了,除非你有营业执照,以公司为主体注册名额还能加两个。比较建议多弄几个微信公众号,只要绑定自己是运营者就行,可以让朋友帮忙注册一下,从微信公众号后台直接爬是有可能被ban接口的,被反爬机制检测到第一次ban一小时,第二次可能ban一天,看情况而定。 我这里是准备了三个微信公众号,保证爬取过程不中断~ 首先进入微信公众号后台,点击图文消息,在跳转的编辑页面上方点击超链接。 在这个页面按F12进入开发者工具,链接内容选择其他公众号,输入你想要爬的公众号名字,点击右边放大镜搜索后对返回数据抓包。 这里第一个返回的数据包是显示公众号搜索内容的,一个重要的参数fakeid就是公众号名字的内部编号。然后返回前面的标头,获取cookie,这是我们登录微信公众号的凭证,后面爬取网页必须带上cookie内容。 点击我们要找的公众号(你名字输对的话肯定是第一个),又返回一个数据包,在负载里我们可以看到begin和count两个重要的参数。在试验过后可以发现,begin表示从哪一页开始,count表示一页显示多少天的推送,这里count值在我多次试验之后,发现最大值为5,传入超过5的数都会变成默认值5(也就是说不能通过一页获取所有文章的url)! 而在响应体中,我们可以看到所有返回文章的title、link、update_time、digest等重要的信息都在app_msg_list中,上面的app_msg_count值我测试后发现是记录一共发布文章天数的。 在这个公众号例子中,digest本来应该是摘要的,但在这里只是一段甚至半段内容,无法提取有用的信息,所以我直接忽略了这部分数据;而且一般公众号会把subtitle分离出来,这个公众号没有,因此需要写一段代码分离标题中的分类标题,以标题中的竖线来分割“副标题(分类)|标题”。 接下来可以随便点一个link,F12看看文章的html结构,记录文章内容的xpath地址(不记得xpath地址怎么找的话,看前一篇xpath用法)。这里文章的图片我就没有收集了,我只收集了文字部分内容。 每个公众号排版不一样,根据内容再写一个正则匹配一下不需要的内容,也就是对内容“去噪”。不详细讲,因公众号而异,我这里要去除的噪声是公众号底部的进群邀请内容。 2. 代码部分截至目前为止(2022年12月28日),代码运行正常: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174import randomimport timeimport requests, refrom requests.packages import urllib3from lxml import etreeimport xlwtfrom pandas import DataFramekey_word = "植物生物技术Pbj"xpath_string = '//*[@id="js_content"]//text()' # 文章内容的xpath路径last_date = 2018 # 想要获得哪一年之后的文章# 创建工作表格,存储爬取的临时数据book = xlwt.Workbook(encoding='utf-8',style_compression=0)sheet = book.add_sheet(key_word,cell_overwrite_ok=True)col = ('title', 'author', 'content','category',"link","date")for i in range(0,6): sheet.write(0,i,col[i]) # 第一行写入属性名称,write对应参数:行、列、值urllib3.disable_warnings() # 忽略警告# 最终爬取数据存放列表title_list = []link_list = []cat_list = []date_list = []content_list = []author_list = []s = requests.Session() # 维持会话# 对微信公众号查找的headersheaders = { 'User-Agent': "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:72.0) Gecko/20100101 Firefox/72.0", "Host": "mp.weixin.qq.com", 'Referer': 'https://mp.weixin.qq.com/'}cookie_str = ""cookies = {}# 加载cookies,将字符串格式的cookies转化为字典形式def load_cookies(): global cookie_str, cookies for item in cookie_str.split(';'): sep_index = item.find('=') cookies[item[:sep_index]] = item[sep_index + 1:]# 去噪函数,只适合该公众号def quzao(content): if type(content) == str: i = re.sub('植物生物技术Pbj交流群', '', str(content)) i = re.sub('为了能更有效地帮助广大的科研工作者获取相关信息.*', '', str(i)) return i else: return ' '# 爬虫主函数def spider(): # 加载cookies load_cookies() # 访问官网主页 url = 'https://mp.weixin.qq.com' res = s.get(url = url, headers = headers, cookies = cookies, verify = False) if res.status_code == 200: # 由于加载了cookies,相当于已经登陆了,系统作了重定义,response的url中含有我们需要的token print(res.url) # 获得token token = re.findall(r'.*?token=(\\d+)', res.url) if token: token = token[0] else: # 没有token的话,说明cookies过时了,没有登陆成功,退出程序 print('登陆失败') return print('token', token) # 检索公众号 url = 'https://mp.weixin.qq.com/cgi-bin/searchbiz' data = { "action": "search_biz", "begin": "0", "count": "5", "query": key_word, "token": token, "lang": "zh_CN", "f": "json", "ajax": "1" } # 继续使用会话发起请求 res = s.get(url = url, params = data, cookies = cookies, headers = headers, verify = False) if res.status_code == 200: # 搜索结果的第一个提取它的fakeid fakeid = res.json()['list'][0]['fakeid'] print('微信公众号fakeid', fakeid) page_size = 5 # 默认是5天文章1页,这个参数似乎最大值只有5 page_count = 278 # 公众号文章总页数(自己手动调整,爬取到第几页) cur_page = 1 # 爬取页数(从第几页开始爬取) l = 1 # excel计数用 while cur_page <= page_count: url = 'https://mp.weixin.qq.com/cgi-bin/appmsg' data = { "action": "list_ex", "begin": str(page_size * (cur_page - 1)), "count": str(page_size), "fakeid": fakeid, "type": "9", "query": "", "token": token, "lang": "zh_CN", "f": "json", "ajax": "1" } time.sleep(random.randint(1, 5)) #继续会话发起请求 res = s.get(url = url, params = data, cookies = cookies, headers = headers, verify =False) if res.status_code == 200: print('开始爬取页数:', cur_page) # 文章列表位于app_msg_list字段中 app_msg_list = res.json()['app_msg_list'] for item in app_msg_list: # 通过更新时间戳获得文章的发布日期 item['post_date'] = time.strftime("%Y-%m-%d", time.localtime(int(item['update_time']))) if int(item['post_date'].split('-')[0])<last_date: continue # 以下标题分离只适合该公众号 if item['title'].find("|") != -1: # 有竖线分离副标题 title = item['title'].split("|")[1].strip() cat = item['title'].split("|")[0].strip() elif item['title'].find("|") != -1: title = item['title'].split("|")[1].strip() # 分离中文竖线 cat = item['title'].split("|")[0].strip() elif item['title'].find("│") != -1: title = item['title'].split("│")[1].strip() # 分离另一种很神奇的竖线 cat = item['title'].split("│")[0].strip() else: title = (item['title']) cat = 'N/A' title_list.append(title) date_list.append(item['post_date']) link_list.append(item['link']) author_list.append(key_word) cat_list.append(cat) response = requests.get(url = item['link'], headers = headers) print("正在解析网页" + str(item['link']) + '......') time.sleep(random.randint(1, 5)) # 爬一个,休息1-5秒 tree_content = etree.HTML(response.text) # 获取爬到的动态页面源码 try: # 解析xpath,去噪 content = tree_content.xpath(xpath_string) content = re.sub(r'\\s+', '', ''.join(content)) # 获取到的文章内容(去空格) content = quzao(content) except: content = '' # 没有内容的可能是内容违规已撤销 content_list.append(content) print('解析文章"'+title+'"成功!') try: # 以下逐行写入,备份数据用,防止反爬造成数据丢失 sheet.write(l , 0, title) sheet.write(l , 1, key_word) sheet.write(l , 2, content) sheet.write(l , 3, cat) sheet.write(l , 4, item['link']) sheet.write(l , 5, item['post_date']) savepath = './微信公众号_' + key_word + '_.xls' l += 1 if l % 20 == 0: # 每20行保存一次(适当调大一点,以免保存失败) book.save(savepath) print("数据备份成功!已保存" + str(l) + "条!") except: continue # 当前页面数+1 cur_page += 1 print('over!开始保存') # 中途没有反爬的话,一次写入所有爬取数据 data = {'title': title_list, 'author': author_list, 'content': content_list,'category': cat_list,"link":link_list,"date":date_list} df = DataFrame(data) df.to_excel('./微信公众号_' + key_word + '_.xlsx') print('保存成功!')spider() 代码只有cookie需要登录微信公众号后台手动获取,复制粘贴进去;page_count由刚才查文章的界面往下拉,找到一共有多少页,其他参数都不用修改。 为了防止半路被反爬,引入了xlwt库,作用是创建工作表,逐行写入保存爬到的临时数据,不然有可能爬到一半被检测到,最后所有数据都不会保存(别问我为什么知道)!最后一步是写入所有数据,名称和临时数据不一样,也是多一步保险措施。爬取过程显示的数据如下: 如果中途被反爬机制检测到,换一个公众号cookie,然后从中断的cur_page处继续,excel另存。 通过以上代码,实现对公众号“植物生物技术Pbj”8447篇推送(从创建的第一天2019年3月1日至2022年12月18日)爬取: 上面的代码是根据“植物生物技术Pbj”这个公众号排版所特制的,一定要注意根据具体公众号决定制作怎么样的去噪函数,总页数其实也可以根据app_msg_count/page_size值写一个函数自动计算出来,但是如果中途被反爬还是要手动改page值,这里就不多此一举了。还有,如果爬取的每页推送天数(也就是第二个data字典中的count值)可以突破5的话,就可以再写一个循环尽量一次拿到多的文章url,这样检测的机率就会大大降低(我每次被检测都是抓取app_msg_list的时候,而不是抓取文章内容的时候)。 经过半天的测试,有一点以下的经验之谈 微信公众号反爬机制可能是检测你翻页的次数,一天翻页的次数在100-200次间比较保险,也就是一次爬500天-1000天内的推送数据 每次抓取数据sleep1-5秒比较靠谱,1-3秒也容易在爬100条左右的时候被抓…… 有一个思路是通过保存所有文章url,再进行每个文章内容抓取,但是获取文章列表的data字典中count值最大只能是5,导致我们需要频繁翻页,这个地方如何突破是一个问题。 sheet.write方法有的时候会失效,在某一次打开excel之后可能没来得及写入数据就被保存,导致后续无法继续保存临时数据。解决方法之一是保存间隔大一点,这里我设置了每写入20行保存一次。 做好爬取数据的双保险!我这里做了临时数据保存,不要抱侥幸心,不然爬半天数据容易全部木大!","categories":[{"name":"网络相关","slug":"网络相关","permalink":"http://www.shelven.com/categories/%E7%BD%91%E7%BB%9C%E7%9B%B8%E5%85%B3/"}],"tags":[{"name":"python","slug":"python","permalink":"http://www.shelven.com/tags/python/"},{"name":"爬虫","slug":"爬虫","permalink":"http://www.shelven.com/tags/%E7%88%AC%E8%99%AB/"}]},{"title":"应用requests、Xpath和selenium编写爬虫脚本","slug":"应用requests、Xpath和selenium编写爬虫脚本","date":"2022-12-13T16:42:48.000Z","updated":"2022-12-13T16:47:45.000Z","comments":true,"path":"2022/12/14/a.html","link":"","permalink":"http://www.shelven.com/2022/12/14/a.html","excerpt":"这篇博客承接前面的HTTP基本原理,对requests、Xpath和selenium三个库/工具做个简单介绍,并且用三个爬虫实例由浅到深理解爬虫的构思和实现过程,最后是用selenium+chromedriver模拟浏览器,实现对微信公众号文章的爬取。","text":"这篇博客承接前面的HTTP基本原理,对requests、Xpath和selenium三个库/工具做个简单介绍,并且用三个爬虫实例由浅到深理解爬虫的构思和实现过程,最后是用selenium+chromedriver模拟浏览器,实现对微信公众号文章的爬取。 1. requestsrequests是python最常见的HTTP客户端库,可以调用requests模块的API伪装成浏览器对网站发起请求。 前面一篇爬虫博客介绍了requests的六种方法,这里不多赘述,主要回顾下发送请求和获得响应的过程。 requests库有两个重要的对象,Request和Response,Request对象对应的是请求,向目标网址发送一个请求访问服务。而Response对象,是包含了爬虫返回的内容。 Request对象完整的发起get和post请求方式: 1234567requests.get(url, params=None, **kwargs)requests.post(url, data=None, **kwargs)# url:想要获取的网页链接# params:显示在url中的参数,字典形式# data:不显示在url中,通过提交表单的方式提交参数,也是字典形式# **kwargs:控制访问的参数,字典形式 当服务器正常响应时,返回状态码200,这个时候就可以用Response对象的属性来获取网页信息。 Response对象属性: .status_code:HTTP响应状态码,200表示成功,其他状态码详见上篇博客 .text:HTTP响应体内容的字符串格式 .content:HTTP响应体内容的二进制格式 .encoding:从HTTP header中猜测的响应内容编码方式 .apparent_encoding:从内容中分析出的响应内容编码方式(备选编码方式) 这里需要注意,如果我们获得的响应内容是图片视频和音频的话,需要用二进制格式进行储存。 有了以上基础知识,就可以用request写一个爬虫项目了,我们现在目的是爬取豆瓣电影古装排行榜前20的电影图片。 进入豆瓣电影排行榜网页,按F12进入浏览器开发者工具,点击网页页面分类中的“古装”,对网页数据进行抓包。当鼠标滚轮往下滚动的时候我们可以发现,每次滚动更新,有一个名字很长的数据包会不断更新,还伴随着一大堆jpg图片的出现,很明显这个数据包是我们要抓取的对象。 点击表头选项的响应头,我们看到返回的数据是json格式,编码方式是utf-8。请求url栏中问号之前的部分是我们要的url,参数可以设置一个字典传入。 在负载部分,多次抓包以后可以看到前三个参数是一直不变的,猜测这几个参数可能是和电影类型和页面布局相关,这个可以不用管。翻页刷新后总是固定显示20个电影,因此limit和数据包内抓取的电影数相关,start和这个数据库中起始的电影编号相关。 再看看响应的json数据中,有一个“cover_url”的键对应值是电影的图片地址,至此思路就很明确了。 1234567891011121314151617181920212223242526import requestsurl = 'https://movie.douban.com/j/chart/top_list'# 传入的url参数设置param = { 'type': '30', # 影片类型代号 'interval_id': '100:90', 'action':'', 'start': '0', # 从第一个影片开始 'limit': '10', # 需要抓取的影片数}# 伪装浏览器headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.46'}# 抓取上面定义范围内所有影片信息response = requests.get(url = url, params = param, headers = headers)# json字符串反序列化为list类型ls = response.json()# 解析电影图片地址,并抓取和保存图片for p in ls: with open('./' + str(p['title']) + '.jpg','wb') as img: response1 = requests.get(url = p["cover_url"], headers = headers) img.write(response1.content) print('over!!!') .json()这个requests库自带的函数还是挺有意思的,在这个例子中是将返回的字符串JSON数据反序列化为list数据,list中嵌套了字典数据,每个电影的信息都储存在字典中。因此这里也可以用.json()['cover_url']直接对图片网址进行抓取,注意下这里第二次抓取的是图片,返回的是二进制数据,所以用content而不是text。 2. Xpath前面例子抓包返回的是JSON字符串,所以可以直接提取我们要的信息。如果返回的是HTML源代码,就可以用正则或者Xpath来解析我们想要的数据。 Xpath是一种解析XML文档信息的工具,我们可以通过lxml库(XML和HTML的解析库)中导入etree模块,实例化etree对象,使用xpath函数结合xpath表达式进行标签定位和指定数据的获取。 Xpath常用规则: 表达式 描述 nodename 选取此节点的所有子节点 / 从当前节点选取直接子节点 // 从当前节点选取所有子孙节点 . 选取当前节点 .. 选取当前节点的父节点 @ 选取属性 Xpath常用表达式: 123456789101112属性定位 //div[@class="slist"] # 找到所有class属性值为slist的div标签层级定位 //div[@class="slist"]/../li[2] # 找到class属性值为slist的div的父标签下的第二个直系子标签li多属性解析 //div[@class="slist" and @name="Id"] # 找到class属性为slist以及name属性为Id的div标签模糊匹配 //div[contains(@class, "slist")] # 找到class属性中包含slist值得div标签文本获取 //li[@class="item"]//text() # 取出class属性值为item的所有li标签下的所有标签文本(包括子标签) 获取属性 //li/a/@href # 取出所有li标签下a标签下的href属性值 etree对象实例化: 1234567本地文件(解析保存在本地的HTML文件):tree = etree.parse(文件名)tree.xpath("xpath表达式")网络数据(实例化一个html类):tree = etree.HTML(网页内容字符串)tree.xpath("xpath表达式") 注意下xpath解析出来的数据是以列表形式存储的,接下来示范一下requests结合Xpath写一个爬虫程序,目的是抓取彼岸图网的4k动漫封面图。 进入页面以后同样F12审查元素,点击不同页抓取返回的数据包,发现翻到第x页能抓到index_x.html,但是第一页没有下划线和其他页稍有不同,为了方便起见就从第二页开始抓。 仔细观察可以发现,所有封面图都在属性值为slist的div标签下的ul标签下的li标签中(这么说着好绕= =),前面http原理的博客说过,这样的标签可以看出是是无序列表,我们要找的封面图片地址可以通过img标签的src属性获得,图片名称可以通过alt属性获得,因此可以写如下的爬虫代码: 1234567891011121314151617181920212223242526272829303132import requestsfrom lxml import etreeimport os# 举个例子,这里抓取的是第二页数据for i in range(2,3): url = 'http://pic.netbian.com/4kdongman/index_'+str(i)+'.html' headers = { 'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.3' } response = requests.get(url = url, headers = headers) # 获取网页内容字符串 page_text = response.text # 实例化etree对象,获取所有图片li标签信息(列表格式) tree = etree.HTML(page_text) li_list = tree.xpath('//div[@class="slist"]/ul/li') # 创建文件夹 if not os.path.exists('./pic'): os.mkdir('./pic') # 解析li标签下孙子标签img的src属性和alt属性 for li in li_list: img_src = 'http://pic.netbian.com' + li.xpath('./a/img/@src')[0] img_name = li.xpath('./a/img/@alt')[0] + '.jpg' # 这里需要处理中文乱码问题,重新编码和解码,gbk是中文最常用的 img_name = img_name.encode('iso-8859-1').decode('gbk') img_data = requests.get(url = img_src, headers = headers).content img_path = 'pic/' + img_name with open(img_path,'wb') as fp: fp.write(img_data) print(img_name,'下载成功!!!') 当然,上面爬虫抓取的只是封面图片,并不是高清图片,因为高清图片是需要登录账号花钱下载的….我们只能合法地从我们能在浏览器中看到的图片爬取信息。而且如果你在短时间内发起大量请求的话,ip是很有可能被封的,以后再讲一些预防反爬的措施。 3. seleniumselenium是一个自动化测试工具,本质是通过驱动浏览器,完全模拟浏览器中的操作,比如拖动、点击下拉等等。为什么要模仿浏览器中的操作呢?因为很多网站是动态加载的,requests这一类的模块无法直接执行JavaScript代码,这个时候就可以通过测试工具selenium模仿人在浏览器中的操作,从而获得网页渲染之后的结果。 selenium官方网站:Selenium (很暖心地有中文文档)以最新的selenium指南为基础,简单介绍一下其用法。 强调两点: selenium在4.3版本做了代码重构,很多方法被改写,最重要的是find_element方法的改写,具体点击这里查看官方消息。本篇博客所有写法将按照最新的语法规则 一定要注意自己的浏览器与驱动版本是否匹配,本篇博客以chrome浏览器为例,chrome浏览器驱动程序官方下载地址请点击http://chromedriver.storage.googleapis.com/index.html 简单地以淘宝首页作为例子: 12345678910111213141516171819202122232425262728from selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.chrome.service import Servicefrom time import sleep# 启动前先将驱动程序放在当前页面bro = Service(executable_path = './chromedriver.exe')# 启动谷歌浏览器driver = webdriver.Chrome(service = bro)# 进入淘宝网页页面driver.get('https://www.taobao.com/')# 标签定位,输入栏id属性值为'q',id属性是整个html中唯一的,不会重复search_input = driver.find_element(By.ID, 'q')# 节点交互,向输入栏中输入数据'Iphone'search_input.send_keys('Iphone')# 标签定位,找到搜索按钮(可以用css选择器、id值或者Xpath等定位,这里用css选择器)btn = driver.find_element(By.CSS_SELECTOR, '.btn-search')# 节点交互,点击搜索按钮btn.click()driver.get('https://www.baidu.com')sleep(2)# 回退driver.back()sleep(2)# 前进driver.forward()sleep(2)# 退出浏览器driver.quit() 简单来说,流程可以分为 创建驱动实例开启会话 driver = webdriver.Chrome() 在浏览器中执行操作 driver.get("https://www.baidu.com") 请求浏览器信息 title = driver.title 建立等待策略(隐式或显示) driver.implicitly_wait(0.5)或者用sleep(1) 定位标签 text_box = driver.find_element(by=By.NAME, value="my-text") 节点交互 text_box.send_keys("Selenium") 获取信息 value = message.text 结束会话,关闭浏览器 driver.quit() 浏览器创建selenium支持的浏览器有Chrome、Firefox、Edge、Internet Explorer和Safari。参考支持的浏览器列表 | Selenium 元素定位webdriver提供了8种内置的定位方法: 12345678910from selenium.webdriver.common.by import Byfind_element(By.ID, 'value') find_element(By.NAME, 'value')find_element(By.CLASS_NAME, 'value')find_element(By.TAG_NAME, 'value')find_element(By.LINK_TEXT, 'value')find_element(By.PARTIAL_LINK_TEXT, 'value')find_element(By,XPATH, 'value')find_element(By.CSS_SELECTOR, 'value') 节点交互常见的节点交互有以下内容: 123456789101112driver.get("https://www.baidu.com") # 打开网站driver.back() # 浏览器后退driver.forward() # 浏览器前进driver.refresh() # 浏览器刷新driver.add_cookie({"name": "key", "value": "value"}) # 当前浏览器添加cookiedriver.find_element(By.LINK_TEXT, "new window").click() # 新窗口中打开链接driver.find_element(By.ID, 'value').send_keys('value') # 定位并发送内容driver.switch_to.new_window('tab') # 打开新标签页并切换到新标签页driver.switch_to.new_window('window') # 打开新窗口并切换到新窗口driver.switch_to.window(original_window) # 切回之前的标签页或窗口driver.close() # 关闭标签页或窗口driver.quit() # 关闭浏览器 动作链上面说的交互是对页面中存在的标签或者说是元素进行交互,而对于没有特定对象的,比对鼠标的双击、拖拽,鼠标滚轮的操作,键盘的输入等,这些操作就需要动作链来执行。该部分内容官网有详细介绍,这里就举个例子了解一下怎么用的就行。 12345678910111213141516171819202122232425import timefrom selenium.webdriver.common.by import Byfrom selenium import webdriverfrom time import sleepfrom selenium.webdriver import ActionChainsbro = webdriver.Chrome(executable_path = './chromedriver')bro.get('https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')# 如果定位的标签是存在于iframe标签之中的则必须通过如下操作在进行标签定位bro.switch_to.frame('iframeResult') # 切换浏览器标签定位的作用域div = bro.find_element(By.ID, 'draggable')# 动作链action = ActionChains(bro)# 点击长按指定的标签action.click_and_hold(div)for i in range(10): action.move_by_offset(5,0) # move_by_offset(x,y):x水平方向 y竖直方向 action.perform() # 执行动作链操作time.sleep(3)#释放动作链action.release()bro.quit() 微信公众号爬虫范例123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596import randomimport refrom pandas import DataFrameimport osimport requestsimport timefrom selenium import webdriverfrom selenium.webdriver.support import expected_conditions as ECfrom selenium.webdriver.support.wait import WebDriverWaitfrom selenium.webdriver.common.by import Byfrom time import sleepfrom selenium.webdriver import ChromeOptionsfrom lxml import etree# moder可以为author或者article,前者为按公众号搜索,后者为按文章关键字搜索modern='author'# 搜索的关键字,如果modern = author,输入公众号名字,否则输入文章关键字keyword = '冷兔'# 爬取多少页,建议先手动搜索最大页码数,page大于最大页码将会报错page = 2headers = { 'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36' }# 实现规避检测option = ChromeOptions()option.add_experimental_option('excludeSwitches', ['enable-automation'])bro = webdriver.Chrome(executable_path = './chromedriver', options=option)bro.get("https://www.sogou.com/index.php")wait = WebDriverWait(bro, 2)search_input = bro.find_element(By.ID, 'query')search_input.send_keys(keyword)# 点击搜索按钮btn = bro.find_element(By.ID, 'stb')btn.click()# 点击微信登陆time.sleep(2)btn = bro.find_element(By.XPATH, '//*[@id="loginBtn"]')btn.click()# 用微信扫码,只有十秒time.sleep(2)btn = bro.find_element(By.XPATH, '/html/body/div[3]/div[3]/div[2]/div[4]/div/a[2]')btn.click()time.sleep(10)button = bro.find_element(By.ID, 'sogou_weixin')button.click()article_button = bro.find_element(By.XPATH, '//*[@id="scroll-header"]/form/div/input[1]')article_button.click()# 最后需要爬取的文章url都在这里url_list=[]for i in range(page): page_text = bro.page_source # 解析 tree = etree.HTML(page_text) author=tree.xpath('/html/body/div[2]/div[1]/div[3]/ul/li/div[2]/div/a/text()') print(author) url_page_list=tree.xpath('/html/body/div[2]/div[1]/div[3]/ul/li/div[2]/h3/a/@href') # 下面这个循环判断按author爬取还是按照aiticle爬取 for j in range(len(author)): if author[j]==keyword: url_list.append(url_page_list[j]) elif modern=='article': url_list.append(url_page_list[j]) else: continue if i != page - 1: next_button = bro.find_element(By.ID, 'sogou_next') next_button.click() time.sleep(2)# 拼接地址url_list=['https://weixin.sogou.com/'+i for i in url_list]title_list=[]content_list=[]time_list=[]author_list=[]for url in url_list: response = bro.get(url=url) time.sleep(random.randint(1, 3)) # 爬一个,休息1-3秒,怕被抓 page_text = bro.page_source tree_content = etree.HTML(page_text) # 获取爬到的动态页面源码 title = tree_content.xpath('/html/body/div[1]/div[2]/div[1]/div/div[1]/h1/text()') title = re.sub(r'\\s', '', ''.join(title)) # 获取到的文章题目 content = tree_content.xpath('/html/body/div[1]/div[2]/div[1]/div/div[1]/div[3]//text()') content = re.sub(r'\\s*', '', ''.join(content)) # 获取到的文章内容 time1=tree_content.xpath('//*[@id="publish_time"]/text()')[0] author=tree_content.xpath('//*[@id="js_name"]/text()')[0] author=re.sub(r'\\s', '', ''.join(author)) title_list.append(title) content_list.append(content) time_list.append(time1) author_list.append(author)# 写入xlsx文件中data = {'title': title_list, 'time':time_list,'author':author_list,'content': content_list}df = DataFrame(data)df.to_excel('./微信公众号_'+keyword+'.xlsx') 这里规避检测识别是设置Chromedriver的启动参数,在启动Chromedriver之前,为Chrome开启实验性功能参数excludeSwitches,它的值为[‘enable-automation’]。这个参数有什么作用呢? 我们正常运行selenium时,最上方是有提示”Chrome正受到自动测试软件的控制“的,这个参数设置就是禁用浏览器的提示。如果我们用selenium模拟chrome浏览器访问网站,网站可以通过检查window.navigator.webdriver返回值判断我们是用正常的浏览器访问还是用selenium模拟浏览器发起的访问。 如果返回值为undefined,说明是正常浏览器;如果返回true说明用selenium模拟的浏览器。 为Chrome开启实验性功能参数excludeSwitches后,和正常浏览器一样返回的是undefined。 当然,反爬手段不仅仅是这一个,这个以后的有空再细说。上面的爬虫代码参考了刘丹老师,不是最终版本,还可以对输出内容和样式继续做优化。简单看一下实现的结果:","categories":[{"name":"网络相关","slug":"网络相关","permalink":"http://www.shelven.com/categories/%E7%BD%91%E7%BB%9C%E7%9B%B8%E5%85%B3/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"http://www.shelven.com/tags/%E7%88%AC%E8%99%AB/"},{"name":"requests","slug":"requests","permalink":"http://www.shelven.com/tags/requests/"},{"name":"Xpath","slug":"Xpath","permalink":"http://www.shelven.com/tags/Xpath/"},{"name":"selenium","slug":"selenium","permalink":"http://www.shelven.com/tags/selenium/"}]},{"title":"HTTP基本原理","slug":"HTTP基本原理","date":"2022-12-10T15:10:45.000Z","updated":"2022-12-10T16:00:15.000Z","comments":true,"path":"2022/12/10/a.html","link":"","permalink":"http://www.shelven.com/2022/12/10/a.html","excerpt":"以前写过一篇博客如何爬取微博热搜的前50条,当时是从代码出发理解爬虫实现的过程。这篇博客主要讲一下HTTP的基本知识,知道从浏览器中输入网址到我们获取网页内容的过程中发生了什么,有助于进一步了解爬虫的基本原理。","text":"以前写过一篇博客如何爬取微博热搜的前50条,当时是从代码出发理解爬虫实现的过程。这篇博客主要讲一下HTTP的基本知识,知道从浏览器中输入网址到我们获取网页内容的过程中发生了什么,有助于进一步了解爬虫的基本原理。 1. URI、URL和URN先放上这三个名词的定义: URI:Uniform Resource Identifier 统一资源标志符 URL:Uniform Resource Locator 统一资源定位符 URN:Uniform Resource Name 统一资源名称 URI是一个抽象定义,只要能定位到一个资源,都叫做URI。 URL和URN都是URI的子集,简单来说,URL用地址定位资源,URN用名称定位资源。只是后来URN在互联网中使用非常少,导致现在几乎所有的URI都是URL,因此我们可以将一般网页链接称之为URI或者URL(后者用的最多)。 那么URL或者URI具体指什么呢? 举个例子,本站图标地址https://www.shelven.com/tuchuang/bitbug_favicon.ico,通过这个地址可以访问到一只32*32像素大小的可爱小猫,通过这个地址URL/URI指定了它的访问方式,包括了访问协议https,访问路径和资源名称bitbug_favicon.ico。这个访问资源可以是一个图片,一个CSS文档,一个HTML页面等等。 以HTTPS协议访问web服务器为例,拆解一下完整的URL结构: https://user:password@www.shelven.com:443/tuchuang/bitbug_favicon.ico 协议:URL开头部分必须是协议类型,常见的https、http、ftp和mailto,指明浏览器应当使用的访问方法,用//做分隔符 用户名/密码:user:password这部分可以省略 域名:我这里域名是www.shelven.com,我们在发送请求前会向DNS服务器解析这个域名的ip地址,域名只是方便我们人类记忆的,计算机访问的最终都是ip地址。当然,如果你能记得住ip地址也可以直接输入。 端口:用来区分不同网络服务(web服务、ftp服务等),和域名之间用冒号:分隔,端口不是URL必须的部分,http默认端口80,https默认端口443,ftp默认端口21。 文件路径/文件名:从域名第一个/到最后一个/之间是虚拟目录;从域名最后一个/到?部分是文件名,没有?则是到#为止,都没有则是从最后一个/一直到结束都是文件名部分。文件名是可以缺省的。 2. 超文本超文本(Hyper Text, HT)是用超链接的方法,将不同空间文字信息组织在一起的网状文本。 举个例子,浏览器中看到的网页就是超文本解析而来的,网页本身就是一个文本文件,而超文本指这种文件既可以包含文本信息,又可以包含图片、视频、链接等非文字信息。 网页的源代码是一系列HTML(Hyper Text Markup Language, 超文本标记语言)代码,里面包含了一系列标签(尖括号<>包围的关键词,一般成对出现)和属性值。在浏览器中打开任意一个页面,按F12就可以打开浏览器的开发者工具,选择元素(Elements)选项卡就可以看到当前网页的源代码,而这些源代码都是超文本。 红框框住的左上角箭头,点击以后可以在页面中用鼠标悬停选中元素,右边对应的源代码部分会高亮,方便我们进行元素审查。 这里顺便记录下HTML常用的标签和属性: 标签名 用法 基本结构标签 HTML标签(根标签) <html></html> 文档头标签 <head></head> 文档标题标签(网页标题) <title></title> 文档主体标签(页面内容) <body></body> 列表标签 (这里因为渲染问题&emsp无法显示空格…知道HTML有缩进的意思就行) 无序列表 <ul type=”disc/circle/square”>&emsp;<li>条目内容</li></ul> 有序列表 <ol type=”1/a/A/i/I”>&emsp;<li>条目内容</li></ol> 定义列表 <dl>&emsp;<dt>列表标题标签</dt>&emsp;<dd>具体列表项</dd></dl> 表格标签 表格标签(tr为行) <table>&emsp;<tr>&emsp;&emsp;<td>单元格内容</td>&emsp;</tr></table> 常用标签 标题标签(h1-h6) <h1>一级标题</h1> 段落标签 <p>这里是内容</p> 字体标签 <font size=”10” color=”black” face=”微软雅黑”>你好</font> 换行标签 <br/> 水平线标签 <hr size=”10” color=”red” width=”50%” align=”left”/> 盒子标签div <div>div标签内容独占一行</div> 盒子标签span <span>span标签内容一行可以多个</span> 图片标签 <img src=”地址” width=’”宽度” height=”高度”></img> 超链接标签 <a href=”跳转网址” target=”窗口弹出方式”></a> 注释标签 <!– 注释内容 –> 还有表单标签<form></form>等等,太多了这里就不一一详细说了,如果以后有必要再出一个详细的HTML笔记,现在只要看到这些标签心里有个数就行,真正要做前端再去详细探究。 3. 协议前面说URL的开头必须指明协议类型,常用的是ftp(文件传输协议)、http(超文本传输协议)、https(http的安全版)、mailto(电子邮件协议)和smb(通信协议)。不需要对所有协议了如指掌,前三中协议是我们日常用的最多的,http和https是我们访问网站web服务所必须的,爬虫也可以通过这两种协议伪装成浏览器访问,从而抓取我们需要的页面。 HTTP(Hyper Text Transfer Protocol, 超文本传输协议)就是一个简单的请求-响应协议,运行在TCP之上,指定客户端发送什么样的消息以及得到什么响应,服务器端实现程序有httpd(本站就是用的这个)和nginx。 HTTPs(Hyper Text Transfer Protocol over Secure Socket Layer)以安全为目标的HTTP通道,说白了就是安全版HTTP,在HTTP下加入SSL层,传输内容经过SSL加密。 本站建站之初,我当时绕了一大圈才签下来SSL证书……HTTPS的安全基础是SSL,主要作用是建立一个信息安全通道,保证数据传输的安全;确认网站的真实性,使用https的网站可以通过浏览器地址栏的锁头标志,查看网站认证的真实信息。 有些网站使用了HTTPs协议但还是会被浏览器提示不安全,那有可能是证书过期了,或者颁发CA证书的机构不是被信任的,这样就会提示”您的连接不是私密连接“。而要用爬虫爬取这种页面的话,需要设置忽略证书,否则会提示SSL证书连接错误。 4. HTTP请求过程我们在浏览器中输入一个 URL,回车之后便会在浏览器中观察到页面内容。这个过程是浏览器向网站所在的服务器发送了一个请求,网站服务器接收到这个请求后进行处理和解析,然后返回对应的响应,接着传回给浏览器。响应里包含了页面的源代码等内容,浏览器再对其进行解析,将网页呈现出来。 以本站作为演示,打开浏览器,按下F12进入开发者工具,点击网络(Network)选项;搜索框输入https://www.shelven.com,回车。观察整个过程中发生了怎样的网络请求。 看下第一个网络请求www.shelven.com,各列的含义如下: 名称name:请求的名称,返回的每一条都是对应的数据包 状态status:响应的状态码,通过状态码判断发送请求之后是否得到正常的响应 类型type:请求的文档类型,这里是返回document,内容就是一些html代码 发起程序initiator:请求源,标记是哪个对象或者进程发起的请求 大小size:从服务器下载的文件和请求资源的大小 时间time:发起请求到获取响应的总时间 时间线waterfall:网络请求的可视化瀑布流 点击具体条目可以看到更详细的信息。 主要看三个部分,常规(general)、响应头(Response Headers)和请求头(Request Headers)。常规部分是总的数据包概括。请求头带有请求信息,例如Cookies、user-agent等信息,服务器会根据请求头内的信息判断请求是否合法,进而作出对应的响应。响应头就是响应的一部分,包含了服务器的类型、文档类型、日期等信息,浏览器接受到响应后,会解析响应内容,进而呈现网页内容。 5. 请求请求指的是从客户端到服务器端的请求消息,发给服务器的请求称为请求报文,可以分为请求行(request line),请求头(request header)和请求体(request body)。 5.1 请求行请求行中包括了请求方法,请求协议和版本。 以百度首页为例: 小框框起来的地方为请求行,可以看到百度首页的请求方法为get,请求协议为HTTP版本1.1 常见的请求方法有两种:GET和POST GET 请求中的参数包含在URL里面,数据可以在URL中看到,而POST请求的URL不会包含这些数据,数据是通过表单形式传输的,会包含在请求体中。 GET 请求提交的数据最多只有1024字节,而POST方式没有限制。 因为GET请求方式不涉及和数据库的交换,所以我们浏览网页用的都是GET请求;如果要在一个网站登录,就需要提交用户名和密码的表单,这个时候用的就是POST请求。还有一个重要的因素,GET方式请求的数据是在URL中完全暴露的,所以也不会用GET方式发送请求,不然容易造成密码泄露。 其他请求方法在前面一篇爬虫博客有提到,这里列个表格: 方法 描述 GET 请求页面,并返回页面内容 POST 大多用于提交表单或上传文件,数据包含在请求体中 HEAD 类似于 GET 请求,只不过返回的响应中没有具体的内容,用于获取报头 PUT 从客户端向服务器传送的数据取代指定文档中的内容 DELETE 请求服务器删除指定的页面 CONNECT 把服务器当作跳板,让服务器代替客户端访问其他网页 OPTIONS 允许客户端查看服务器的性能 TRACE 回显服务器收到的请求,主要用于测试或诊断 表格参考:HTTP 请求方法 | 菜鸟教程 (runoob.com) 5.2 请求头请求头是用来说明服务器使用的附加信息的,上面那个百度首页例子的大框框住部分就是请求头的信息。 同样列个表格记录下常用的头信息。 头信息 描述 Accept 请求报头域,用于指定客户端可接受哪些类型的信息 Accept-Language 指定客户端可接受的语言类型 Accept-Encoding 指定客户端可接受的内容编码 Host 指定请求资源的主机IP和端口号 Cookie 而存储在用户本地的数据,主要功能是维持当前访问会话 Referer 标识请求是从哪个页面发过来的,服务器可以拿来做来源统计、防盗链处理 User-Agent 服务器识别客户使用的操作系统及版本、浏览器及版本等信息,爬虫伪装浏览器必备 Content-Type 请求的数据类型信息HTTP Content-type 对照表 (oschina.net) 5.3 请求体请求体承载POST请求中的表单数据,GET请求的请求体为空。 这里以登录github捕获的请求体为例: 登录的时候填写的用户名和密码信息,提交的时候就会以表单形式提交给服务器,这个时候可以看到请求头中的Cotent-Type为application/x-www-form-urlencoded,表示以表单数据的形式提交给服务器。可以设置不同的Cotent-Type,以不同的方式提交数据,如果在做爬虫的时候要构造POST请求,需要注意一下使用正确的Cotent-Type(类型/子类型),不然可能会提交后无法正常响应。 Cotent-Type 数据提交的方式 application/x-www-form-urlencoded 表单数据 multipart/form-data 表单文件上传 application/json 序列化JSON数据 text/xml XML数据 application/pdf pdf格式 application/octet-stream 二进制流数据(如常见的文件下载) 6. 响应和请求类似的,服务器进行HTTP响应也是分为三个部分:响应状态行,响应头和响应体 6.1 响应状态行回到之前百度首页的例子,我们点开百度首页审查元素,这次点开响应那一栏查看源。 小框框起来的地方是响应状态行,我们可以看到响应的协议版本是HTTP/1.1,响应状态码是200,说明返回正常。 响应状态码其实并不陌生,顾名思义表示服务器的响应状态。200说明服务器正常响应返回正常数据,经常能看到404报错,代表的是页面未找到,403表示服务器拒绝执行请求,503代表服务器不可用,301代表网页被永久转移到其他URL。 因为状态码非常多,这里就记录一下状态码的分类,详细状态码列表可以参考HTTP 状态码 | 菜鸟教程 (runoob.com) 分类 分类描述 100-199 信息响应,服务器收到请求,需要请求者继续执行操作 200-299 成功响应,操作被成功接收并处理 300-399 重定向,需要进一步的操作以完成请求 400-499 客户端错误,请求包含语法错误或无法完成请求 500-599 服务器错误,服务器在处理请求的过程中发生了错误 6.2 响应头上面例子中红色大框框住的部分就是响应头,包含服务器对请求的应答信息。 响应头主要有如下的信息: 头信息 描述 Date 标识响应产生的时间 Last-Modified 指定资源的最后修改时间 Content-Encoding 指定响应内容的编码 Server 服务器的信息,比如名称、版本号 Content-Type 返回的数据类型信息 Set-Cookie 设置 Cookies,下次请求会携带这个cookies Expires 指定响应的过期时间 6.3 响应体响应体就是响应的内容,请求网页的时候响应体就是对应的HTML源代码,请求一张图片,响应体就是返回的二进制数据。爬虫就是通过请求到网页后,解析响应体中的内容(有的时候是HTML代码,有的时候是JSON数据等等,这两者比较常见),然后从中提取我们要的信息。 在edge浏览器中,进入开发者工具,点击network选项,选中需要解析的项目名称,点击响应就可以看到返回的响应体数据了。 以上暂时总结这么多HTTP的基础,参考了相当多的内容,后面做爬虫练习自然会用到。","categories":[{"name":"网络相关","slug":"网络相关","permalink":"http://www.shelven.com/categories/%E7%BD%91%E7%BB%9C%E7%9B%B8%E5%85%B3/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"http://www.shelven.com/tags/%E7%88%AC%E8%99%AB/"},{"name":"HTTP","slug":"HTTP","permalink":"http://www.shelven.com/tags/HTTP/"}]},{"title":"python自学笔记(7)——模块、包和库","slug":"python自学笔记(7)——模块、包和库","date":"2022-11-29T14:30:03.000Z","updated":"2022-12-03T15:51:32.000Z","comments":true,"path":"2022/11/29/b.html","link":"","permalink":"http://www.shelven.com/2022/11/29/b.html","excerpt":"前面介绍递归函数的时候用到了sys模块,介绍文件操作函数的时候用到了os模块,之前只是简单说了这两个模块下部分函数的用法,这里详细介绍一下对于模块、包和库的概念,以及一些常见的模块用法。","text":"前面介绍递归函数的时候用到了sys模块,介绍文件操作函数的时候用到了os模块,之前只是简单说了这两个模块下部分函数的用法,这里详细介绍一下对于模块、包和库的概念,以及一些常见的模块用法。 不需要记住每个模块下所有函数用法,但是平常看到python文件导入模块操作的时候,要大概知道这几个模块有什么作用。 1. 概念1.1 模块(module)函数可以理解为完成特定功能的一段程序,类是包含一组数据及操作这些数据或传递消息的函数的集合,而模块(module)是在函数和类的基础上,将一系列相关代码组织到一起的集合体。 在python中,扩展名为.py的源程序文件就是一个模块,这个和C语言的头文件以及JAVA的包是类似的。 python官方网站上可以查看当前标准库中的所有模块,点击这里。 1.2 包(package)为了方便调用将一些功能相近的模块组织在一起,或是将一个较为复杂的模块拆分为多个组成部分,可以将 .py 源程序文件放在同一个文件夹下,按照 Python 的规则进行管理,这样的文件夹和其中的文件就称为包(package)。 包的目录下需要创建__init__.py 模块,可以是一个空文件,可以写一些初始化代码,其作用就是告诉 Python 要将该目录当成包来处理,让python认为你这是一个包而不是单纯的一个目录(否则会显示找不到包)。有的博客说python3.3版本之后不需要空的__init__.py 模块来声明这是一个包了,但是我在vscode和jupyter运行python3.10的时候发现还是需要__init__.py 模块声明的,这里先存疑,我保留自己的观点。 2022.12.3更新:准确来说,从包里导入模块需要__init__.py 声明;直接导入同目录下的模块不需要(3.3版本以后) 简单来说,包就是有层次地文件目录结构,里面装着各种扩展名.py的python源程序文件,包中也可以含有包。 1.3 库库顾名思义则是功能相关联的包的集合。python的三大特色之一:强大的标准库,第三方库以及自定义模块。 2. 常用模块/库python的三大特色对应三种类型的模块,标准库的内置模块,第三方库开源模块和自定义的模块,这里简单记录一下常用的模块/库。 模块名称 介绍 内置模块 os 普遍的操作系统功能接口,包括前面介绍的文件操作函数 sys 提供了一系列有关Python运行环境的变量和函数,sys.path.append() random 生成随机数,random() 返回0<n<=1 time 各种提供日期、时间功能的类和函数,time.time() 时间戳 datetime 对time模块的一个高级封装 logging 日志打印到了标准输出中 re 可以直接调用来实现正则匹配,re.split() 分割字符串,格式化列表 pymysql 连接数据库,并实现简单的增删改查 threading 提供了更强大的多线程管理方案 json 用于字符串和数据类型间进行转换json subprocess 像linux一样创建运行子进程 shutil 对压缩包的处理、对文件和文件夹的高级处理,os的补充 tkinter Python的标准Tk GUI工具包的接口 第三方模块/库 Requsests python最有名的第三方HTTP客户端库 Scrapy 屏幕抓取和web抓取框架,编写爬虫用到(上面的也可以) Pillow 常用的图像处理库 Matplotlib 绘制二维数据图的库,使用方式对标matlab NumPy 提供大型矩阵计算公式,在很多领域都用到 Pandas 基于Numpy 和 Matplotlib,和上面两个组成数据分析三剑客 Django 开源的web开发框架 PyTorch 开源的深度学习框架,各种张量操作、梯度计算,方便构建各种动态神经网络 TensorFlow 也是机器学习库,张量的操作和运算,tensorboard可视化数据很强大 第三方库实在太多,这里只列举了我知道的比较常见的库;内置模块可以见1.1章节的官网链接,里面有所有内置模块的具体用法。接下来说说怎么导入模块和制作模块。 3. 导入包和模块3.1 导入模块制作模块要注意,自定义的模块名不能和系统内置的模块重名,否则被重名的系统模块无法被导入。 python中用关键字import引入某个模块,在调用模块中的函数时,需要以 模块名.函数名 的方式进行引用。自定义模块名中的函数是可以重名的,因为模块名不会相同(同一层目录下文件名不同),调用的时候可以进行区分,这很好理解。 3.2 导入包有的时候我们只需要包里的某个模块或者模块里的某个函数,而不需要包或者模块里的全部内容,这个时候我们可以用关键词 from 包名/模块名 import 模块名/函数名 来进行调用。 举个例子,在如下的文件结构中,main.py作为主程序入口,test文件夹相当于一个包,里面有4个.py后缀的模块,分别定义了四则运算的函数,__init__.py 是个空文件(暂时不做处理),声明test文件夹是个python包而不是普通的目录。 123456789101112131415# add.py文件内容——定义加法运算def add(a, b): return a + b# sub.py文件内容——定义减法运算def sub(a, b): return a - b# mul.py文件内容——定义乘法运算def mul(a, b): return a * b# dev.py文件内容——定义除法运算def dev(a, b): return a / b 我现在要做的是,在main.py文件里,导入test包里四个模块,调用各自模块中对应的函数,有以下几种调用方式: 123456789101112131415161718192021222324252627282930# main.py的文件内容import test.add # 导入test包下的add模块import test.sub as sb # 导入test包下的sub模块,并重命名为sbfrom test import mul # 从test包中导入mul模块from test.dev import dev # 从test包的dev模块导入dev函数,注意这里导入的是函数def calculate(x, y, operate): result = 0 if operate == '+': result = test.add.add(x, y) # 调用test.add模块中的add函数 elif operate == '-': result = sb.sub(x, y) # test.sub被重命名为sb,调用sb中的sub函数 elif operate == '*': result = mul.mul(x, y) # 调用mul模块中的mul函数 else: result = dev(x, y) # dev函数已经被导入,可以直接调用函数名 return resultprint(calculate(100, 100, '+'))print(calculate(100, 100, '-'))print(calculate(100, 100, '*'))print(calculate(100, 100, '/'))'''运行结果:2000100001.0''' 4. 包和模块导入的思考4.1 __init__.py的作用在上面的例子中__init__.py 是个空文件,是声明test文件夹是python包所必须的(主程序和包的位置在同一个目录下)。然而我们在编写main.py的主程序文件的时候,仍然要在开头导入相当多的模块,比较繁琐,这个时候可以在__init__.py中批量导入我们所需要的模块(导入包其实就是导入__init__.py文件)。 12345678910# 在__init__.py中添加如下内容import test.addimport test.subimport test.mulimport test.devadd = test.add.addsub = test.sub.submul = test.mul.muldev = test.dev.dev 123456789101112131415161718192021222324252627# main.py相应的改为如下内容from test import * # 导入包相当于执行包下的__init__.py,这个文件已经将包里的四个模块分别导入了def calculate(x, y, operate): result = 0 if operate == '+': result = add(x, y) elif operate == '-': result = sub(x, y) elif operate == '*': result = mul(x, y) else: result = dev(x, y) return resultprint(calculate(100, 100, '+'))print(calculate(100, 100, '-'))print(calculate(100, 100, '*'))print(calculate(100, 100, '/'))'''运行结果:2000100001.0''' 可以看到上面的主程序代码量少了很多,起到简化代码的作用。 4.2 if __name__ == ‘__main__‘首先来看一个现象,如果在add.py文件中不仅仅有定义函数的代码,还有编写代码时做的测试内容,如下: 12345# add.py文件中最后一行对这个函数做了测试def add(a, b): return a + bprint(add(3, 4)) 其他文件全都不变,再次运行main.py,会发现输出结果为: 123457 # add.py中测试内容也被输出2000100001.0 这显然不是我们想看到的,我们在导入add模块调用add函数的时候,并不想要其他无关的输出结果。 稍稍改变一下add.py内容 123456# add.py文件内容def add(a, b): return a + bif __name__ == '__main__': print(add(3, 4)) 此时再次运行main.py则不会输出add.py中的测试内容。 首先要了解一个概念,在每个python文件创建的时候都有一个记录名称的变量__name__,当这个python文件作为脚本直接运行,那么__name__的值为‘”__main__“;当这个文件作为模块被导入其他文件中运行的时候,这个__name__的值为模块的名字,也就是说 当.py文件被直接运行时,if __name__ == ‘__main__‘ 之下的代码块将被运行 当.py文件以模块形式被导入时,if __name__ == ‘__main__‘ 之下的代码块不被运行 在导入的模块中有选择性地执行代码,这在实际开发应用中非常普遍。 4.3 导入模块在主程序的父目录下前面的导入模块操作,导入模块要么在主程序的子目录下(加入__init__.py 声明这是一个包),要么和主程序在同一个目录(直接import),如果导入模块在主程序的父目录下,应该怎么导入呢? 首先,按照一般流程直接import导入和加入__init__.py声明都会报错找不到这个包,这里就不演示了。 其实这个问题在前面的笔记中有记录,点击这里。 当时是刚用vscode搭建python环境,对python调用一知半解都算不上,现在才有了初步的理解。 12345678910# 两种解决办法# 1.在主程序内部临时添加python运行环境路径import syssys.path.append('父目录绝对路径或者相对路径')import module# 缺点:只能调用一次(临时加入的环境变量路径),且每个想要导入的自定义模块都要写一次,比较麻烦# 2.在python安装目录下Libsite-packages中创建扩展名为.pth的文件,添加想要加入的路径。# python在遍历已知的库文件目录过程中,如果见到一个.pth 文件,就会将文件中所记录的路径加入到sys.path设置中,于是.pth文件指向的地址也就可以被Python运行环境找到了。# 这个已知的库文件目录可以通过sys.path查看。 4.4 相对导入前面3.2的例子中,包和模块的导入都是用的绝对导入(absolute import),导入时写明了工作环境中包的具体位置。 还有一种导入方式称为相对导入(relative import),还是用3.2的例子理解一下,在如下的文件结构中,主程序入口main.py和test包在同一层目录下,test包中有__init__.py(空文件),add.py和dev.py两个模块的内容如下: 123456789101112# add.py内容def add(a, b): return a + b# dev.py内容from .add import add # 相对导入,从当前导入包的目录中找到add模块def dev(a, b): return a / bdef func1(a, b): a = dev(a, b) + add(a, b) return a 上面的例子意思是,我现在要在test包的dev.py模块中用add.py模块的函数方法(同一个包中的模块相互引用,这在实际工程中很常见)。如果dev.py是主程序,我们可以直接import add;但是我们这里main是主程序,代码如下: 1234# main.py内容from test import devprint(dev.func1(1, 2)) 主程序main.py功能是导入test包dev模块,打印dev模块的函数func1(1, 2)执行结果。 如果我们在dev.py中直接导入from add import add(没有点,也就是不加当前目录),这个时候再运行main.py会报错找不到模块(因为main.py同目录下没有add.py模块)。这个时候就有两种导入方式,要么完善包名字,使用绝对导入from test.add import add;要么使用相对导入from .add import add。 这个相对导入就像是linux的文件操作方式,一个点代表当前目录,两个点代表父目录,还能用三个点表示linux无法做到的祖父目录,依此类推。 相对导入的优点就一目了然:就算改变了包的名字,这个时候调用也不会出错,也就是简化代码,方便迁移。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"编程自学","slug":"编程自学","permalink":"http://www.shelven.com/categories/%E7%BC%96%E7%A8%8B%E8%87%AA%E5%AD%A6/"}],"tags":[{"name":"python","slug":"python","permalink":"http://www.shelven.com/tags/python/"}]},{"title":"python自学笔记(6)——函数的变量和高级用法","slug":"python自学笔记(6)——函数的变量和高级用法","date":"2022-11-28T18:37:37.000Z","updated":"2022-12-03T15:54:38.000Z","comments":true,"path":"2022/11/29/a.html","link":"","permalink":"http://www.shelven.com/2022/11/29/a.html","excerpt":"前面在通过讲什么是高阶函数(能够接受函数作为参数传入的函数,或者可以返回函数对象的函数)引出了装饰器的由来和存在的意义。这里对python函数的其他基础概念做个补充和记录。","text":"前面在通过讲什么是高阶函数(能够接受函数作为参数传入的函数,或者可以返回函数对象的函数)引出了装饰器的由来和存在的意义。这里对python函数的其他基础概念做个补充和记录。 1. 函数的变量之前笔记中的例子已经对函数参数传递过程做了总结,提到了怎么调用函数的返回值,怎么实现函数的嵌套,基本概念用法都已经提过,这里只是做个思考和补充。 1.1 局部变量 函数内部的定义的变量 局部变量只在函数内部生效,不同函数可以拥有同名的局部变量,互不影响(作用域为本函数) 局部变量的作用,为了临时保存数据需要在函数中定义变量来进行存储 局部变量在函数执行时被创建,函数执行完成后,局部变量会被系统回收 1234567891011121314151617# 局部变量重名互不影响,只作用在当前函数中def added(a, b): c = a + b return cdef connect(a, b): c = str(a) + str(b) return cprint(added(4, 5))print(connect('Phantom', 'Aria'))'''运行结果:9PhantomAria''' 1.2 全局变量 函数外部定义的变量 全局变量可以在多个函数中使用(作用域为所有函数) 全局变量如果和局部变量重名,只会使用局部变量(就近原则) 如果在函数中修改不可变类型全局变量,需要使用global声明 这里有一个很有意思的现象,前面在数据类型里说过,数据可以分为可变数据类型(列表,字典,集合)和不可变数据类型(数字,元组,字符串),而python的所有参数传递都是引用传递而非值传递。因此,对于可变类型的全局变量,在函数中可以被修改;而对于不可变全局对象则无法在函数中直接修改,其本质是修改不可变数据系统会创建一个新的对象(分配一个新的内存地址),然而这个对象名已经被占用了(也就是变量名无法被指向,原来的变量名也没有被收回)。下面举个栗子。 1234567891011121314151617181920212223# 不可变类型全局变量在函数内部可以传递使用但是无法直接修改x = 100def added(): x = x + 1 # 不可变数据修改,系统会创建新的对象,而变量名x已经是全局变量的变量名,无法成为新的对象的变量名 return xadded()'''---------------------------------------------------------------------------UnboundLocalError Traceback (most recent call last)d:\\zhuomian\\python\\test.ipynb Cell 47 in <cell line: 6>() 3 x = x + 1 4 return x----> 6 added(4)d:\\zhuomian\\python\\test.ipynb Cell 47 in added(a) 2 def added(a):----> 3 x = x + 1 4 return xUnboundLocalError: local variable 'x' referenced before assignment''' 对于global是如何运作,使得python解释器可以将不可变全局变量进行修改的?这点以我的功底还无法解释……暂时只能知道是这么个用法。 顺带一提,还有个嵌套函数对外围函数的不可变变量进行修改,需要用到类似的nonlocal进行声明。 1234567891011121314151617181920212223242526x = 100def added(): global x # global声明x为全局变量 x = x + 1 print(x)added()added()def A(): y = 200 def B(): nonlocal y # nonlocal声明y为外围函数的变量(不是全局变量!) y = y + 1 return y return Btest = A()print(test())print(test())'''运行结果:101102201202''' 上面的例子本质上是一样的,对于嵌套函数来说,要修改外围函数的不可变类型的变量(看起来似乎矛盾,不可变的怎么能叫变量呢?前面已经说过,重新赋值造成数字和字符串看起来是“可变的”假象,这里分清两个变分别指什么意思),相当于是上面例子的在函数内修改不可变类型的全局变量(作用域不同,只能说相当于),只不过二者声明的方式不同。 1.3 修改可变全局变量引起的思考一个很有意思的现象: 123456789101112131415161718a = [1, 2, 3]def add1(ls): ls = ls + lsb = [1, 2, 3]def add2(ls): ls += lsadd1(a)print(a)add2(b)print(b)'''运行结果:[1, 2, 3][1, 2, 3, 1, 2, 3]''' a列表和b列表都是可变全局变量,同一个算法,为什么a在传入函数执行之后没有发生改变呢? 这里需要对可变数据类型做个回顾,可变对象可以对自身内容进行原地修改而不改变存储地址。原地修改画个重点,意思是利用方法比如reverse、sort、append等在原有对象上直接修改。 ‘=’ 是赋值语句,将右边的表达式的结果对象,引用绑定到等号左边的变量名上。赋值是创建一个新对象,赋值给目标,返回的也是新对象,引用地址会发生改变。 ‘+=’ 是增强赋值语句,对左边的对象进行原地修改,返回值为None,引用地址不变。 看到这里就能明白上面两个看似“同样”的操作为什么会返回不一样的结果,也加深了“可变”与“不可变”的理解。 2. 函数的高级用法前一篇笔记写的装饰器就是函数的高级用法之一,这里做个完善补充。 2.1 匿名函数除了用def关键字命名函数这种基础方法之外,还可以使用lambda表达式创建匿名函数。 lambda语法格式如下: 1lambda param1,...paramN:expression 匿名函数的语法比较简洁,能接受任何数量的参数但只能返回一个表达式的值。因为匿名函数比较简洁小巧,也常用在作为参数进行传递。 12345678910111213141516# 定义匿名函数func1 = lambda x, y : x + yresult = func1(1, 2)print("匿名函数func1执行结果:",result)# 匿名函数作为参数传递def func2(x, y, opt): print('函数func2执行结果为:', opt(x, y))func2(4, 5, lambda x, y : x + y)'''运行结果:匿名函数func1执行结果: 3函数func2执行结果为: 9''' 2.2 嵌套调用相比来说函数嵌套调用可能算不上是高级用法,不过这里还是补充一下。嵌套调用指一个函数里调用另一个函数,注意和嵌套函数区分。 一个简单的例子: 1234567891011121314def func1(): # 定义一个函数 print('第一个函数输出Phantom')def func2(): # 定义第二个函数 func1() # 在第二个函数里调用第一个函数功能 print('第二个函数输出Aria')func2() # 执行一个函数,实际上两个函数都执行了一遍'''运行结果:第一个函数输出Phantom第二个函数输出Aria''' 2.3 递归函数递归函数就是在一个函数内部调用自身的函数,本质上是一个循环,循环结束的点就是递归出口。 用阶乘举个最简单的例子: 1234567891011121314151617181920212223# 使用迭代实现阶乘算法def factorial(n): result = 1 for i in range(2, n +1): result *= i i += 1 return result# 使用递归实现阶乘算法def factorial_1(n): if n == 1: return 1 else: return n * factorial_1(n - 1)print(factorial(10))print(factorial_1(10))'''运行结果:36288003628800''' 迭代的方法,从1开始,进入for循环对之前的结果累积乘以 i,直至 n(上例函数被调用了1次)。 递归的方式更为直观,每次通过递减数字的方式递归调用自己(上例函数被调用了10次)。 整体上看递归更简洁明了,但是相比迭代会占用更多内存,运行时间会更长。 递归有最大深度限制,在计算机中,函数名、参数、值类型等,都是存放在栈上的。每进行一次函数调用,就会在栈上加一层,函数返回就减一层,由于栈的大小是有限的,递归次数过多就会导致堆栈溢出。 可以调用sys模块,sys.setrecursionlimit(2000)将栈的大小调整为2000,sys.getrecursionlimit()查看当前设置的最大递归深度。这种调整递归深度的方式不是无限大的,我的jupyter在调用递归函数3000次的时候就会直接退出……模块定义和调用方式后一篇笔记再说。 3. 文件操作函数3.1 open() & close()函数open()可以打开一个文件,或者创建一个新文件,函数close()可以关闭文件。两者语法如下: 12345f = open('文件名', '访问模式')f.close() # 注意最后一定要有close()with open('文件名', '访问模式') as f: # 自动调用close() f.方法() 访问模式 说明 r 只读方式打开文件,默认模式,打开文件必须存在。 w 写入方式打开文件,已存在的文件会覆盖内容(相当于linux重定向操作符>)。 a 追加方式打开文件,已存在的文件会将内容写到最后(相当于linux重定向操作符>>)。 x 只写方式打开文件,新建一个文件,若文件存在则报错。 r+ 读写方式打开文件,打开文件必须存在。 w+ 读写方式打开文件,已存在的文件会覆盖内容。 a+ 读写方式打开文件,已存在的文件会将内容写到最后。 一般用 with open() as 的方式打开文件,这种方式会自动帮我们调用f.close() 3.2 write() & read()write()向文件写入数据,以w方式访问,如果文件名存在会先清空文件内容,文件名不存在则新建;以a方式访问,如果文件名存在则续写,文件名不存在则新建;以r方式访问则报错。 read()从文件中读取数据,括号里面的参数代表读取的数据长度(字节数),如果不传入参数则读取所有数据。 readline()读取一行,同时会读取一行最后的换行符\\n,所以打印出来的时候会多一行空行。 readlines()按照行的方式读取整个文件数据,返回的是一个列表,每行数据是一个元素,同样会读到换行符\\n并且显示出来。 需要注意一点,在多次读取的操作中,后一次读取会从上一次读完的位置开始。 123456789101112131415161718192021222324252627with open('test.txt', 'w') as f: # 只写模式创建一个新文件 f.write('My name is Phantom. \\nI am Aria.')with open('test.txt', 'a') as f: # 追加模式进行续写 f.write('\\nWell, it\\'s been so long.')'''生成的test.txt内容: # 实际前面两行末尾都有换行符My name is Phantom.I am Aria.Well, it's been so long.''' with open('test.txt', 'r') as f: # 只读方式打开文件 line = f.read(1) # read读取第一个字节 print(line) line = f.readline() # readline读取第一个字节后的第一行,因为读取了换行符,所以运行结果多一行空行 print(line) line = f.readlines() # readlines读取接下来的两行,每行数据为一个元素,返回一个列表 print(line) '''运行结果:My name is Phantom. ['I am Aria.\\n', "Well, it's been so long."]''' 3.3 os模块的文件操作函数os模块和上面递归函数最后提到的sys模块用的非常多,下篇笔记再详细说明,这里就记一下用法。 这几个函数也非常直观,举个例子就知道分别有什么作用: 1234567import osos.rename('test.txt', 'TEST.txt') # 文件重命名os.remove('TEST.txt') # 文件删除os.mkdir('./test') # 创建文件夹,文件夹存在的话会报错,且只能创建一级目录os.rmdir('./test') # 删除文件夹os.makedirs('./TEST/TEST1/TEST2') #递归的方式创建多级目录","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"编程自学","slug":"编程自学","permalink":"http://www.shelven.com/categories/%E7%BC%96%E7%A8%8B%E8%87%AA%E5%AD%A6/"}],"tags":[{"name":"python","slug":"python","permalink":"http://www.shelven.com/tags/python/"}]},{"title":"python自学笔记(5)——装饰器","slug":"python自学笔记(5)——装饰器","date":"2022-11-27T19:03:55.000Z","updated":"2022-12-03T15:55:01.000Z","comments":true,"path":"2022/11/28/a.html","link":"","permalink":"http://www.shelven.com/2022/11/28/a.html","excerpt":"这里讲一讲前面提到的python装饰器,@classmethod和@staticmethod是python内置装饰器,在了解什么是装饰器之前首先要了解函数的几个特征。","text":"这里讲一讲前面提到的python装饰器,@classmethod和@staticmethod是python内置装饰器,在了解什么是装饰器之前首先要了解函数的几个特征。 1. 有关函数的几个概念1.1 函数可以接收另一个函数作为参数传入高阶函数可以接收另一个函数作为传入的参数: 12345678910111213def func1(a, b): return a + b# 高阶函数,函数func2接收函数作为参数传入def func2(func, m, n): return func(m, n)func2(func1, 1, 2) '''运行结果:3''' 从上面例子可以看到,在执行 func2函数的时候,函数对象func1作为参数被传入func2,返回func1(1, 2)的执行结果也就是3. 1.2 函数可以把另一个函数作为结果返回高阶函数也可以将函数作为结果返回: 123456789101112131415161718# 高阶函数,把函数作为结果返回def func1(): pass def func2(): # 内层函数(嵌套函数) print('执行func2函数') return func2 # 返回内层函数的引用a = func1() # 返回的函数对象func2的引用赋值给aprint(a) # 打印函数对象,获得存储地址a() # 执行内层函数func2()的功能'''运行结果:<function func1.<locals>.func2 at 0x00000288D3701750>执行func2函数''' 上面的例子可以看到,外围函数 func1将内层函数 func2的引用赋值给a,此时a就有了内层函数func2 的方法,此时打印的a是函数的存储地址,执行a() 就可以执行func2 函数的功能。 1.3 嵌套函数可以引用外层函数的变量稍稍修改1.2的例子,在外层函数添加局部变量msg: 123456789101112131415def func1(): # 外围函数 msg = 'I am Phantom' def func2(): # 内层函数(嵌套函数) print(msg) return func2a = func1() # 实际上这里获得的就是一个闭包a() # 引用外层函数的变量,执行内层函数func2()的功能'''运行结果:I am Phantom''' 这里先引用闭包的概念: 闭包:指延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量。概念比较晦涩,简单来说就是嵌套函数引用了外层函数的变量。 这个例子和上个例子唯一的区别是,msg是一个在外围函数中的局部变量,在print_msg()函数执行之后应该就不会存在了。但是嵌套函数引用了这个变量,将这个局部变量封闭在了嵌套函数中,这样就形成了一个闭包。 有了以上关于高阶函数和闭包的概念后,就可以开始理解什么是装饰器以及装饰器的作用了。 2. 装饰器decorator装饰器的本质就是一个闭包,把一个函数当做参数然后返回一个替代版函数(函数的引用)。 2.1 @标识符将装饰器应用到函数下面将用代码方式简单演示装饰器是怎么应用的。 1234567891011121314151617181920def func1(func3): def func2(): print(f'被装饰的函数{func3.__name__}即将执行') func3() # 被装饰的函数 print(f'被装饰的函数{func3.__name__}执行结束') return func2def funcx(): print('函数正在运行')a = func1(funcx) # 1a() # 2'''运行结果:被装饰的函数funcx即将执行函数正在运行被装饰的函数funcx执行结束''' 上面这个例子就是不用@标识符的装饰器,首先我定义了一个函数func1,它只有一个func3参数,这个函数里面定义了一个嵌套函数func2。func2的作用是调用func3前打印一串字符,然后执行被装饰的函数func3,结束之后再打印一串字符。 我们再定义一个测试函数funcx,功能是打印一段“函数正在运行”的字符串。 在1处,函数func1中传入函数funcx,返回函数func2的引用赋值给变量a,此时并没有执行函数,也不会有打印结果。在2处执行了func2函数,前面传入的函数funcx作为参数在原先的func3处执行,这个时候就会依次输出三行字符串。 将@标识符应用到函数上,只需要在函数定义前加上@和装饰器的名称即可。 1234567891011121314151617181920def func1(func3): def func2(): print(f'被装饰的函数{func3.__name__}即将执行') func3() # 被装饰函数 print(f'被装饰的函数{func3.__name__}执行结束') return func2@func1def funcx(): print('函数正在运行')funcx()'''运行结果:被装饰的函数funcx即将执行函数正在运行被装饰的函数funcx执行结束''' 这里@func1就是装饰器,它接受被装饰的函数作为参数传入,返回内部嵌套函数的引用(注意这个时候并没有执行函数),内部嵌套函数func2持有被装饰函数func3的引用。 可以看到@语法只是将函数传入装饰器函数,并不是什么特别难理解的概念,主要作用就是节省代码量(避免了再一次的赋值操作)。 2.2 带参数的装饰器前面示范的是不带参数的装饰器,带参数的装饰器也是类似的,我们只要知道装饰器最终返回的一定是嵌套函数的引用。在前面的参数传递博文中,我们说过*args和**kargs可以以包裹传递的方式传递不定长参数,这里也是一样的。 12345678910111213141516171819202122232425262728def func1(func3): def func2(*args, **kargs): print(f'被装饰的函数{func3.__name__}即将执行') func3(*args, **kargs) print(f'被装饰的函数{func3.__name__}执行结束') return func2@func1def funcx(a, b): print(a + b)@func1def funcy(a, b, c): print(str(a) + str(b) + str(c))funcx(1, 2)print('*************************')funcy('Phan', 't', 'om')'''被装饰的函数funcx即将执行3被装饰的函数funcx执行结束*************************被装饰的函数funcy即将执行Phantom被装饰的函数funcy执行结束''' 上面的装饰器带的参数都是我们后面自定义函数里的参数,装饰器的语法允许我们在调用时提供其他参数。 1234567891011121314151617181920212223242526272829303132#import functoolsdef func1(text): def decorator(func): # @functools.wraps(func) def func2(*args, **kwargs): if text == 'Phantom': print('%s 正在运行' % func.__name__) print(*args) print(text) return func(*args, **kwargs) return func2 return decorator @func1(text = "Phantom")def funcx(a): print(funcx.__name__)funcx('test')'''注释的运行结果:funcx 正在运行testPhantomfunc2*************************去掉注释的运行结果:funcx 正在运行testPhantomfuncx''' 先不看导入的模块,后面再解释。 上面的例子看上去很复杂,可以一层一层剥开理解。func1是允许带参数的装饰器,实际上是原有装饰器decorator的再一次封装,并且返回了这个装饰器,可以理解为含有一个形参text的闭包。当我们使用@func1(text = “Phantom”)时,python解释器将我们的实参“Phantom”传入到装饰器的环境。 而嵌套函数func2在检查到传入的text参数与字符串“Phantom”相同时,就会执行后面的打印函数名、funcx传入的实参和func1传入到decorate的实参。 通过特殊属性__name__可以看到,funcx函数指向了装饰器内部定义的func2函数,也就是经过装饰器装饰后丢失了原函数的元信息,我们真正调用的是装饰后生成的新函数。那么是不是每次都要使用func2.__name__ = func.__name__这样的代码来保留原函数信息呢?并不是,我们可以使用functools库中的@functools.wraps()来保留原函数的属性,其实这种保留只是将原始被装饰的函数的属性拷贝给了装饰函数,如果不干这件事,有些依赖函数签名的代码执行就会出错,感兴趣的小伙伴可以继续探究~ 2.3 内置装饰器上面说的@functools.wraps()其实也是内置装饰器,下面介绍其他几个常用的内置装饰器。 2.3.1 @property这个内置装饰器用来装饰类函数,被装饰的类函数不可以在类被实例化后调用,只能通过访问与函数同名的属性进行调用(也就是把类的方法伪装成属性)。 12345678910111213141516171819class A(): def func1(self): print('Phantom') @property def func2(self): print('Aria') a = A() # 实例化一个对象a.func1() # 通过实例化对象访问类方法a.func2 # 通过实例化对象将类方法伪装成属性调用'''运行结果:PhantomAria''' 我们知道属性是可以被赋值的,但是经过property装饰的方法不可以像普通属性那样被赋值。 这个特性很有意思,我们可以实现对python类私有属性的安全访问(再次强调不存在严格意义的私有属性)。 1234567891011121314151617181920212223242526272829class A: __number = 'Phantom' # 类内的私有属性 @property def number(self): return self.__numbera = A()try: print(a.__number) # 尝试直接访问类内的私有属性失败except: print("访问私有属性失败")try: print(a.number) # 通过类方法伪装的属性访问私有属性成功 print("访问私有属性成功")except: passtry: a.number = 1 # 类方法伪装的属性无法被赋值except: print("修改私有属性失败")'''运行结果:访问私有属性失败Phantom访问私有属性成功修改私有属性失败''' 2.3.2 @classmethod直接翻译,这个装饰器就是用来定义类方法的,被装饰的函数必须有一个cls参数用来绑定类本身,隐式地将类作为对象,传递给方法,调用地时候不需要进行实例化。 如果不加这个装饰器,必须要使用self参数,隐式地将类实例传递给方法,也就是说必须要实例化。 强调一点,这里地cls和self只是为了方便编程的时候一眼看出来绑定的是类还是对象,都可以用别的xxx名字代替(但是不建议)。 12345678910111213141516class A(): def func1(self,x,y): # 实例方法 return x * y @classmethod def func2(cls,x,y): # 类方法 return x * y print(A().func1(5,5)) # 必须实例化A()之后通过实例化对象才可以调用方法print(A.func2(5,5)) # 不需要实例化,直接通过类对象调用'''运行结果:2525''' 由于被classmethod装饰的函数强制暴露了类自身,所以我们可以通过被classmethod装饰的函数对类的静态变量进行一定操作,在实例化之前和类进行交互。还有类方法可以通过实例对象或者类对象去访问,所以有一个用途就是通过实例调用类方法实现对类属性的修改(点击见第三篇博客例子)。 2.3.3 @staticmethod前面博客介绍过,这个装饰器是声明静态方法的,静态方法和上面的类方法一样,不需要实例化就可以直接调用,但是这个方法不强制要求传递参数,无法直接使用任何类变量、类方法或者实例方法、实例变量(这里要注意,只有主动传参才可以调用,因为主动传参是可以按照逻辑去找需要的参数的)。 1234567891011121314151617class People(): name = 'Phantom' def __init__(self, name = 'Aria'): self.name = name @staticmethod def getName(): print('静态方法调用类属性', People.name) #print(self.name) #不能调用实例的属性,会报错,名义上是类方法,实际已经和类无关p = People()p.getName() # 可以通过 类.方法名 或者 实例.方法名 进行调用 '''运行结果:静态方法调用类属性 Phantom''' staticmethod更像是与实例无关但与类封装功能有关的函数,如果有一个功能实现的方法比较独立,可以考虑用静态方法来实现。 在继承类中,staticmethod和classmethod有以下区别 子类的实例继承了父类的@staticmethod静态方法,调用该方法,还是调用的父类的方法和类属性。 子类的实例继承了父类的@classmethod类方法,调用该方法,调用的是子类的方法和子类的类属性。 1234567891011121314151617181920212223242526272829class A(): name = 'Phantom' @staticmethod def func1(): return print(A.name) @classmethod def func2(cls): return print(cls.name) class B(A): name = 'Aria'a = A()a.func1()a.func2()print('***********************')b = B()b.func1()b.func2()'''运行结果:PhantomPhantom***********************PhantomAria''' 上面这个例子可以看出来,@classmethod装饰后的func1函数实际上已经和父类没什么关系了,尽管在父类方法里但也可以当作是个独立的函数,不管子类的实例化还是父类的实例化都是调用同一个函数,输出结果一致。而@classmethod装饰后的func2函数,cls参数绑定了类本身,子类在实例化后继承了父类@classmethod类方法,但是调用的是子类的方法和类属性。 所有装饰器存在的意义都是为函数扩展功能,总结以下几点: 装饰器通过高级函数、嵌套函数和闭包实现 装饰器返回闭包函数的引用,这个闭包函数引用中有被装饰函数的引用 装饰器通过语法糖 @ 修饰 装饰器不修改原函数和调用方式(调用的是装饰后的新函数)","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"编程自学","slug":"编程自学","permalink":"http://www.shelven.com/categories/%E7%BC%96%E7%A8%8B%E8%87%AA%E5%AD%A6/"}],"tags":[{"name":"python","slug":"python","permalink":"http://www.shelven.com/tags/python/"}]},{"title":"python自学笔记(4)——面向对象编程(下)","slug":"python自学笔记(4)——面向对象编程(下)","date":"2022-11-26T15:59:03.000Z","updated":"2022-12-03T15:55:35.000Z","comments":true,"path":"2022/11/26/a.html","link":"","permalink":"http://www.shelven.com/2022/11/26/a.html","excerpt":"前面说到python中一切皆为对象,面向对象是python的核心,也通过代码方式了解了什么是类和对象、属性和方法以及具体的分类。这篇笔记主要记录下前面没讲完的面向对象编程具体的三个特征。","text":"前面说到python中一切皆为对象,面向对象是python的核心,也通过代码方式了解了什么是类和对象、属性和方法以及具体的分类。这篇笔记主要记录下前面没讲完的面向对象编程具体的三个特征。 面向对象编程的特征python是面向对象的语言,支持面向对象的三大特征:封装(隐藏),继承和多态。 1. 封装(隐藏)1.1 封装概念隐藏对象的属性和实现细节,只对外提供必要的方法。相当于将“细节封装起来”,只对外暴露“相关调用方法”。 通过私有属性、私有方法(都是在属性或者方法前加上__来实现私有化,类外部不能直接访问)的方式,实现封装(Encapsulation)。封装的概念类似权限控制,有些属性或方法只想于类别内部使用,而不想公开于外部,除了减少代码因来源端不适当的使用发生问题外,也可保护其中重要的商业逻辑。 当然,前面说过python没有严格意义上的访问控制限制,更多还是靠编程人员的自觉= = 2 继承2.1 继承概念继承是创建新类的方式,是实现代码复用的重要手段(比如一个新类继承自设计好的类,就直接具备已有类的特征,减少代码重复编写)。对于已有的类,我们称为父类或基类,而要创建的新类,我们称为子类或派生类。python支持多继承,也就是新建的类可以有一个或者多个父类。 python3中默认继承object类,object是根类,是所有类的父亲。编写过程中object可以省略。 12345678910111213141516171819202122232425262728# 定义一个父类(object可省)class Animal(object): def __init__(self, name, color): self.name = name self.color = color def eat(self): print('%s在进食' % self.name)# 定义一个子类,括号中为父类的名字class Pig(Animal): def setName(self, newname): self.name = newname return self.namePeggy = Pig('猪', '粉色') # 实例化对象print('Peggy是%s,颜色是%s' % (Peggy.name, Peggy.color)) # 查看对象属性Peggy.eat() # 调用父类方法print('现在Peggy的名字叫做%s' % Peggy.setName('George')) # 调用子类方法print(Pig.__mro__) # 查看类的继承层次结构,可以用类属性__mro__或者类方法mro()'''运行结果:Peggy是猪,颜色是粉色猪在进食现在Peggy的名字叫做George(<class '__main__.Pig'>, <class '__main__.Animal'>, <class 'object'>)''' 从上面的例子可以看到,子类Pig从父类Animal中继承了__init__()方法,从子类中实例化对象Peggy是可以调用父类的方法的。 需要注意: 私有的属性和方法(前面带有__)不能被子类继承,也不能被访问! 2.2 多继承顾名思义一个子类继承自多个直接父类,这样也就有了多个父类的特点。 123456789101112131415161718192021# 定义一个父类马class Horse: def output(self): print('骡子的一半基因来自马')# 定义一个父类驴class Donkey: def output(self): print('骡子的一半基因来自驴')# 定义一个子类骡子,继承自马和驴class Mule(Horse, Donkey): passa = Mule() # 实例化一个对象骡子a.output() # 调用同名父类方法print(Mule.__mro__)'''运行结果:骡子的一半基因来自马(<class '__main__.Mule'>, <class '__main__.Horse'>, <class '__main__.Donkey'>, <class 'object'>)''' 上面的例子可以看到,子类骡子(Mule)继承自父类马(Horse)和驴(Donkey),这样可以拥有两个父类各自的特征。但是,如果父类中如果有同名的方法(这里的output(self)),那么子类只会从左到右的顺序,调用先继承的父类(Horse)中的方法。 同样可以通过类属性__mro__来查看继承结构,显示结果也是从左到右的顺序,从子类开始一层层往上到父类,这就是继承的顺序。 一般情况下不建议用多继承(一个人不可能有两个爹),代码可读性会变差。 2.3 重写父类方法重写的意思是,当子类中有一个和父类相同的名字的方法,子类中的方法会重新定义覆盖掉父类中的方法。 123456789101112131415class Animal: def eat(self): print('是个动物都会进食')class Pig(Animal): def eat(self): print('只有猪才会吃了睡睡了吃')Peggy = Pig()Peggy.eat() # 调用父类同名方法,子类的方法会覆盖'''运行结果:只有猪才会吃了睡睡了吃''' 如果想要在子类方法中调用父类的同名方法,最简单的实现方式是在子类方法中进行类调用,但是父类名如果修改过,在多继承时子类的方法也要重复改很多次,python为了解决这个问题引入了super()函数,需要注意super()代表父类的定义,而不是父类对象。 123456789101112131415class Animal: def eat(self): print('是个动物都会进食')class Pig(Animal): def eat(self): super().eat() # super()代表父类名,即使父类名改变这里也不需要改Peggy = Pig()Peggy.eat()'''运行结果:是个动物都会进食''' 一般而言,super在继承中经常用来继承父类的初始化方法,例如 super().__init__() 3 多态3.1 多态概念多态指不同对象对同一个方法调用,可能会产生不同的行为。举个栗子,对于同样一个吃饭的方法,不同对象比如中国人用筷子吃饭,印度三哥用手抓饭,欧美人用刀叉吃饭。 需要注意以下几点: 多态是方法的多态,属性没有多态 多态存在的必要条件:继承和方法重写 3.2 代码演示多态和“鸭子类型”12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152class People: passclass Chinese(People): def eat(self): print('中国人用筷子吃饭')class Indian(People): def eat(self): print('印度人用手抓饭')class American(People): def eat(self): print('欧美人用刀叉吃饭')# 分别实例化,并定义一个统一的接口来使用XiaoMing = Chinese()ASan = Indian()George = American()def Eatting(self): self.eat()Eatting(XiaoMing) # 相当于调用了XiaoMing.eat,以下同理Eatting(ASan)Eatting(George)print('*******************************************')# “鸭子类型”# 定义三个不同的类(实际上也都继承自根类object)class Chinese: def eat(self): print('中国人用筷子吃饭')class Indian: def eat(self): print('印度人用手抓饭')class American: def eat(self): print('欧美人用刀叉吃饭') People_list = [Chinese, Indian, American] # 封装好的类作为People_list的元素for person in People_list: person().eat() # person()是实例化对象的过程,分别调用不同类的同名方法 '''运行结果:中国人用筷子吃饭印度人用手抓饭欧美人用刀叉吃饭*******************************************中国人用筷子吃饭印度人用手抓饭欧美人用刀叉吃饭''' 不同对象调用同名方法,产生不同结果,这就体现了多态性,好处在于增强了程序的灵活性和可扩展性。 Python崇尚的“鸭子类型”就是动态类型的风格:“当看到一直鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以称为鸭子。”这种动态风格中,一个对象的有效语义,不是由继承自特定的类或实现特定的接口,而是由当前方法和属性的集合决定,也就是说,我们并不关心对象是什么类型,而是关心对象是怎么使用的。 总而言之,这种动态类型使得编程非常灵活,可以避免一些重写和继承,省去复制大量重复代码的操作。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"编程自学","slug":"编程自学","permalink":"http://www.shelven.com/categories/%E7%BC%96%E7%A8%8B%E8%87%AA%E5%AD%A6/"}],"tags":[{"name":"python","slug":"python","permalink":"http://www.shelven.com/tags/python/"}]},{"title":"python自学笔记(3)——面向对象编程(上)","slug":"python自学笔记(3)——面向对象编程(上)","date":"2022-11-25T15:31:53.000Z","updated":"2022-12-03T15:55:58.000Z","comments":true,"path":"2022/11/25/a.html","link":"","permalink":"http://www.shelven.com/2022/11/25/a.html","excerpt":"面向对象是Python的核心概念,一开始在这些概念问题上一直绕不清,这里做个简单记录。","text":"面向对象是Python的核心概念,一开始在这些概念问题上一直绕不清,这里做个简单记录。 1. 面向对象编程使用计算机语言编写代码时,有两种思路分别是面向过程编程和面向对象编程 面向过程:根据业务逻辑从上到下,直接分析解决问题的步骤,调用函数实现。强调怎么去做 面向对象:将问题分解成若干“对象”,建立对象是为了描述某个事物在解决问题过程中的行为。强调谁去做 面向过程注重步骤和过程,所有步骤从到到尾逐步实现,将功能独立的代码封装成函数,最后完成代码就是按照顺序地调用不同函数。 面向对象注重对象和职责,确认职责,根据职责确定不同对象,对象内部封装不同的方法,最后完成代码是按照顺序让不同对象调用不同方法。 python是面向对象编程思想的一门语言,包括做机器学习或者深度学习用的PyTorch、TensorFlow都是面向对象的思想,里面封装了非常多的方法,我们甚至可以不知道方法具体实现的过程和原理,直接调用函数就可以(初学的我就是一开始依葫芦画瓢,程序能跑通但是不能解释实现的原理),对于小白的入门学习确实提供了极大便利(然后一出问题就开始恶补基础了)。 2. 概念性名词先要了解概念性的专业名词,再通过代码的方式加深自己的理解。 面向对象有三个特性,封装性、继承性和多态性(下一篇博客再细说)。 封装性:把属性和方法放在一个类里面,可以通过访问类的权限属性区分开,不想释放的功能搞成私有机制 继承性:把实现好的代码和方法通过继承的方法拿过来用,节省代码量 多态性:同一个方法用不同的方式去实现,体现的多态性 先解释一下上面提到的几个专有名词: 对象(object):python中一切皆对象,对应现实生活中,任何事物都可以称为对象,有自己独特的特征。对象是通过类创建出的真实的个体(对象是类的实例化),对象由属性和方法组成。 类(class):具有同种属性的对象,现实世界中具有共同特征的事物为一类,比如人类,植物类等,描述的是所有对象的共有特征。拥有相似属性和行为的对象都可以抽象出一个类。 属性(attribute):属于对象静态的一面,描述对象的一些静态特征,比如小明的身高、体重、年龄等。 方法(method):属于对象动态的一面,描述对象的动态特征,比如小明会说话,会码代码等。 实例化:对象由一个别名叫“实例”,通过类创建对象的过程为“实例化”。 抽象:由相同特征的对象抽取共同特征的过程为“抽象”。 3. 代码方式理解类和对象开头的class来创建一个新的类,class之后为类的名称(通常首字母大写)并以冒号结尾。 12345678910111213141516171819# 定义类class People: # 定义方法 def getPeopleInfo(self): print('名字:%s, 年龄:%d' %(self.name, self.age))# 实例化一个对象 Phantom = People()Phantom.name = 'Phantom' # 使用 . 的方法添加类属性Phantom.age = 26Phantom.getPeopleInfo() # 使用 .函数名() 的方法调用类中创建的函数print(Phantom.age) # 打印实例Phantom的年龄属性'''运行结果:名字:Phantom, 年龄:2626''' 在创建的类中定义方法,而类中的方法和普通的函数有一个区别——必须有一个额外的第一个参数名称, 按照惯例是 self。self指的是实例的本身,指向当前创建对象的内存地址。某个对象调用其方法时,python解释器会把这个对象作为第一个参数传递给self,所以我们只需要传递后面的参数。 python是没有方法的重载的,如果定义了多个重名的方法,只会生效最后一个! 在上面的例子里我给Phantom添加了两个对象属性:name和age,但是如果再实例化一个其他对象,能否在创建时就给予属性而不用重新添加呢?答案是肯定的,这个时候我们可以用__init__()函数来定义属性的默认值。 1234567891011121314151617181920212223242526class People: # 初始化函数,使对象的属性具有默认值 def __init__(self, sex = 'male', age = 26): self.sex = sex self.age = age # 定义类方法 def getPeopleInfo(self): print('性别:%s, 年龄:%d' %(self.sex, self.age)) # 创建对象Phantom,不传参,属性使用默认值Phantom = People()print(Phantom.sex, Phantom.age)Phantom.getPeopleInfo()# 创建第二个对象Aria,传参,新的参数代替默认值Aria = People('Female', 24)print(Aria.sex, Aria.age)Aria.getPeopleInfo()'''运行结果:male 26性别:male, 年龄:26Female 24性别:Female, 年龄:24''' 可以看到上面创建对象Phantom后,我没有传入参数,python解释器立刻调用了__init__()函数给与了两个属性sex和age,这个时候再调用类内的方法getPeopleInfo(),就会将属性的默认值作为实参传入。 __init__(self)中只有一个默认参数self,如果创建对象传入了两个实参,那么除了self以外还需要两个形参,比如__init__(self, sex, age)这个和自定义创建的类方法不一样,一定要做区分,后面会说到。这里的self是不需要我们传递的,python解释器会自动把当前对象的引用传递进去。 4. 代码方式理解属性和方法4.1 类属性类拥有的属性分为公有属性(public)和私有属性(private),python对于类的属性没有严格的访问控制限制,这与其他面向对象语言有所区别。 _xxx 保护属性,python编辑器不会做任何处理,是给程序员看的,不希望被外部访问 xxx 自己定义的公有属性 __xxx 类中的私有属性,不能从外部直接访问,但是可以通过 实例._类名__私有属性 的方式访问 再次强调,python不存在严格意义上的私有属性。 12345678910111213141516171819# 创建类,object是对象,可以省略class People(object): name = 'Phantom' # 公有的类属性 __age = 26 # 私有的类属性 _sex = 'male' # 保护的类属性 def __init__(self): pass # 空语句,占位用,不会执行任何操作p = People()print(p.name) # 通过实例对象访问公有类属性print(p._People__age) # 通过实例访问私有类属性print(p._sex) # 访问保护的类属性(可以访问但是不推荐)'''运行结果:Phantom26male''' 4.2 实例属性实例属性是从属于实例对象的属性。 实例属性可以在__init__()方法中通过 self.实例属性名 = 初始值 的方式进行定义 实例属性可以修改、新增和删除,不会影响到类属性 123456789101112131415class People(object): name = 'Phantom'p = People()print(p.name, People.name) # 通过实例查看实例属性,通过类对象查看类属性p.name = 'Aria' # 修改实例属性print(p.name, People.name)People.name = 'Aria' # 修改类属性print(p.name, People.name)'''Phantom PhantomAria PhantomAria Aria''' 可以看到通过一个实例对象去引用修改,只是修改了实例属性而不会影响到类属性。 4.3 特殊属性Python 对象中包含了很多双下划线开始和结束的属性,这些是特殊属性,有特殊的用法。 特殊方法 含义 obj.__dict__ 对象的属性字典 obj.__class__ 对象所属的类 class.__bases__ 类的基类元组(多继承) class.__base__ 类的基类 class.__mro__ 类层次结构 class.__subclasses__ 子类列表 实际操作运行几个简单的例子: 12345678910111213141516171819202122class People: name = 'Phantom' # 类属性 def __init__(self, sex, age): # 实例属性 self.sex = sex self.age = agep = People('male', 26)# dict 生成类属性信息的字典和实例对象属性信息的字典print('People类属性为:' + str(People.__dict__))print('实例对象p属性为:' + str(p.__dict__))# class 对实例对象查询所属类信息print('实例对象p所属类信息:' + str(p.__class__))'''运行结果:People类属性为:{'__module__': '__main__', 'name': 'Phantom', '__init__': <function People.__init__ at 0x000001A7D212AB00>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None}实例对象p属性为:{'sex': 'male', 'age': 26}实例对象p所属类信息:<class '__main__.People'>''' 4.4 实例方法和类方法在类中以def开头定义的方法都是实例方法,实例方法的特点是必须有一个以上的参数(self),用于指定这个方法的实例对象。 类方法也是最少需要一个参数(cls),是类对象有的方法,需要使用装饰器@classmethod来标识其为类方法,关于装饰器的概念我后面再写一篇博客,这里简单按照字面意思理解一下。类方法可以通过实例对象或者类对象去访问,有一个用途就是通过实例调用类方法实现对类属性的修改。 1234567891011121314151617181920class People(): name = 'Phantm' @classmethod def getName(cls): return cls.name @classmethod def setName(cls, name): cls.name = namep = People()print(p.getName(), People.getName()) # 通过实例和类对象调用类方法,先查看一下类属性p.setName('Aria') # 通过实例调用类方法改变类属性print(p.getName(), People.getName())'''运行结果:Phantm PhantmAria Aria''' 4.5 静态方法python是动态的语言,我们可以动态地为类添加新的方法,或者动态地修改已有的方法。静态方法可以理解为不变的方法,不依赖于实例对象也不依赖于类对象,因此无论是实例对象还是类对象都可以调用。如果有一个功能实现的方法比较独立,可以考虑用静态方法来实现,静态方法需要使用装饰器@staticmethod来标识。 需要注意的是,静态方法无法使用实例的属性和方法。 1234567891011121314151617class People(): name = 'Phantom' def __init__(self, name = 'Aria'): self.name = name @staticmethod def getName(): print('静态方法调用类属性', People.name) #print(self.name) #不能调用实例的属性,会报错p = People()p.getName()'''运行结果:静态方法调用类属性 Phantom''' 4.6 特殊方法前面的普通方法都是通过 对象名.方法名() 的方式调用,和前面有特殊属性一样,python也有一些特殊方法(或者叫魔术方法),这些特殊方法在符合条件的时候自动触发,不需要调用。 因为特殊方法非常多,这里只简单记录一些常用的。 特殊方法 含义 构造类 __new__(cls, […]) 对象实例化时调用的第一个方法,第一个参数时类,其他参数传递给__init__(),决定是否使用 __init__(self, […]) 构造器,当一个实例被创建时调用的初始化方法 __del__(self) 构造器,当实例对象被销毁时调用的方法 表示类 __str__(self) 描述类或对象信息,比如打印实例化对象,返回定义内容(给人看) __repr__(self) 描述类或对象信息,比如打印实例化对象,返回定义内容(给解释器看) 访问控制类 __setattr__(self, key, value) 定义当一个属性被设置时的行为 __getattr__(self, key) 定义用户试图获取一个不存在的属性时的行为 __delattr__(self, key) 定义当一个属性被删除时的行为 __getattribute__(self, key) 定义当该类属性被访问时的行为(所有属性/方法调用都要经过这里) __dir__(self) 定义当dir()被调用时的行为 比较操作类 __eq__(self, other) 判断两个对象是否相等 __ne__(self,other) 判断两个对象是否不相等 __lt__(self, other) 定义小于号的行为:x < y 调用 x.__lt__(y) __gt__(self, other) 定义大于号的行为:x > y 调用 x.__gt__(y) 容器类 __setitem__(self, key, value) 定义设置容器中指定元素的操作,相当于 self[key] = value __getitem__(self, key) 定义获取容器中指定元素的操作 ,相当于 self[key] __delitem__(self, key) 定义删除容器中指定元素的操作 ,相当于 del self[key] __len__(self) 定义当被 len() 调用时的操作,即返回容器中元素个数 __iter__(self) 定义迭代容器中的元素的操作 __contains__(self, item) 定义当使用成员测试运算符(in 或 not in)时的操作 __reversed__(self) 定义当被 reversed() 调用时的操作 可调用对象类 __call__(self, [args…]) 使实例对象以 对象名() 的形式使用 这些特殊方法比较常用,看到知道是怎么一回事就好。容器类的特殊方法稍微解释一下,python中常用字典、元组、列表和字符串作为容器,它们都实现了容器协议,可迭代。最后一个调用对象类特殊方法写个代码描述一下 1234567891011121314151617181920class Calculate(): def __init__(self, x, y): self.x = x self.y = y def __call__(self, m, n): self.m = m self.n = n SUM = m + n return SUMa = Calculate(100, 200) print(a(111, 222)) # __call__() 将实例化对象a当作一个方法来执行print(a.x, a.y) # 实例属性并没有改变'''运行结果:333100 200''' 在上面这个例子中,首先初始化了一个Calculate实例a,调用 __init__() 方法,给与了实例属性x和y以及对应的值。但是对于实例对象a又做了调用 a(111, 222) ,实际上调用的是 __call__() 方法,传入自定义参数实现自己的逻辑,这在类实现一个装饰器的场景中比较常见。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"编程自学","slug":"编程自学","permalink":"http://www.shelven.com/categories/%E7%BC%96%E7%A8%8B%E8%87%AA%E5%AD%A6/"}],"tags":[{"name":"python","slug":"python","permalink":"http://www.shelven.com/tags/python/"}]},{"title":"python自学笔记(2)——运算符和参数传递","slug":"python自学笔记(2)——运算符和参数传递","date":"2022-11-24T09:05:33.000Z","updated":"2022-12-03T15:57:53.000Z","comments":true,"path":"2022/11/24/a.html","link":"","permalink":"http://www.shelven.com/2022/11/24/a.html","excerpt":"本篇笔记主要记录python基础的运算符和函数参数传递,以及自己学习过程的一些思考。","text":"本篇笔记主要记录python基础的运算符和函数参数传递,以及自己学习过程的一些思考。 1. 运算符1.1 比较运算符python中常见的比较运算符如下: == 检查左右两个值是否相等,相等则返回True != 检查左右两个值是否相等,不相等则返回True <> 和 != 一样,检查两个值是否相等,不相等返回True >= 字面意思,字面意思的还有<=、 < 和 > 1.2 算数运算符python常见的算数运算符: / 两个数相除,结果为浮点型 // 两个数相除,结果为向下取整的整数 % 取模,也就是两个整数相除的余数 ** 幂运算,返回乘方的结果 + 两个数相加,或者字符串相连 * 两个数相乘,或者返回重复若干次的字符串 - 字面意思,两个数相减 1.3 赋值运算符顾名思义都是在赋值的时候用到的运算符 = 常规赋值运算,运算结果赋值给变量 += 加法赋值运算,a += b等效于a = a+b 其他算数运算符都可以后面跟上=,进行运算后赋值 1.4 位运算符按位运算就是将数字转换为二进制来运算的运算形式,数值是用补码来表示和存储的,计算机用位运算符进行四则运算速度快。但是我们平常可能用不到,这里稍微记录一下。 & 按位“与”:两个值如果相应位都为1,则结果为1,否则0 | 按位“或”:两个值相应位有一个位1,结果就为1 ^ 按位“异或”:两个值相应位相异,结果为1 ~ 按位“取反”:对数据的每个二进制位取反 << 左移运算符:运算数的二进制全部座椅若干位,高位丢弃,低位补0;>>右移同理 1.5 逻辑运算符 and 逻辑“与”,两个都为True则返回True,否则False or 逻辑“或”,两个至少有一个True则返回True,否则False not 逻辑“非”,字面意思 1.6 成员和身份运算符python的成员运算符用来判断一个数据是否在指定的序列或者集合中,而身份运算符是用来判断两个变量是否引用自同一个对象。 in 成员运算符,在指定序列中找到值则返回True,否则False not in 成员运算符,在指定序列中没有找到值则返回True,否则False is 身份运算符,两个标识符是否引自同一个对象,是则返回True,否则False is not 身份运算符,两个标识符是否引用同一个对象,不是则返回True,否则False 是否引自同一个对象,简单理解就是看存储的内存位置是否一样,通过函数id()可以查看变量在内存中的存储位置。 2. 参数传递要理解参数传递的过程,首先要明白关于函数参数的两个具体概念:形参和实参 定义时小括号中的参数,是用来接收参数的,称为“形参”,可以是缺省参数、不定长参数 调用时小括号中的参数,是用来传递参数给函数的,称为“实参” 向函数传递实参的方式很多,确定传递参数个数可以使用位置实参或者关键字实参,不确定传递参数个数可以使用包裹(packing)传递的方式,来包裹位置或者关键字实参,进行参数传递。 2.1 位置实参函数调用时每个实参都要关联到函数定义中的一个形参,最简单的是按照形参的位置从左到右按照顺序传递,位置参数必须一一对应,缺一不可。 比如上面创建了一个describe_me()函数,形参定义了需要name和age两个参数,因此在调用这个函数的时候要按照顺序提供这两个参数。在上例中,从左到右实参‘Phantom’储存在形参name中,实参26储存在形参age中,参数传递本质上就是实参到形参的赋值操作。 2.2 关键字实参关键字实参顾名思义是传递函数的key-Value对,在实参中将关键字和值关联,因为这种对应关系是唯一的,在调用函数的时候就不需要考虑实参的顺序。 这种参数传递方式比较直观,能一眼看出函数调用时各个值对应的用途。 关键字实参和位置实参时可以一起使用,需要注意: 关键字实参必须在位置实参右边(写的时候位置实参优先) 对同一个形参不可重复传值 2.3 形参的缺省创建函数的时候可以给形参指定默认值(缺省)。 对于缺省形参,需要注意: 缺省参数要在非缺省参数之后(缺省形参放右边) 缺省参数是可选参数,可以不传;如果传入则按照传入的值进行运算 2.4 形参的不定长参数(包裹传递)当传入的参数个数不确定时,可以使用包裹位置参数和包裹关键字参数进行参数传递。 *+形参的方式传递参数,传入后根据参数的位置以元组形式保存 **+形参的方式传递参数,需要使用关键字,传入后以字典形式保存,形参名字是传入字典的键 上面例子形参中的args表示arguments位置参数,kargs表示key arguments关键字参数,这个是可以自定义的。 不同的参数传递方式可以混用,原则上要遵循位置参数,默认参数,包裹位置,包裹关键字的顺序。 1def func(name, age = 26, *args, **kargs) # 定义和调用都遵循这样的顺序 2.5 对传参的思考在CSDN上看到一个总结写的很好,函数的参数传递本质上是从实参到形参的赋值操作,而所有的赋值操作都是“引用的赋值”,因此Python中参数的传递都是“引用传递”,不是“值传递”。这句话初看有点难以理解,首先看看什么是引用传递和值传递: 值传递(pass by value):调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。 引用传递(pass by reference):调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。 根本区别在于引用传递不创建副本,最直接的理解方式就是通过查看对象的地址,看看传参前后对象在内存的位置是否改变。 2.5.1 可变对象的传递复习一下,可变对象有列表、字典和集合,我们这里以列表为例。 可以看到,如果传递的对象是可变对象,实际上传递的是对象的引用(不创建副本,传递前后在内存中存储位置不变),在函数中修改对象后,直接在原始对象上做了相应的修改。 2.5.2 不可变对象的传递如果传递的对象是不可变类型,比如元组,字符和数字,这里以数字为例。 可以看到,如果传递的对象是不可变对象,传进函数的时候同样是对象的引用,但是不可变对象无法修改,因此在赋值操作时,系统新创建了一个对象(和原来a的存储地址不同)进行赋值,而原始对象并没有改变。 也就是说,不可变对象的传递起到类似值传递的效果,但是实际上依然是引用传递的方式进行传参。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"编程自学","slug":"编程自学","permalink":"http://www.shelven.com/categories/%E7%BC%96%E7%A8%8B%E8%87%AA%E5%AD%A6/"}],"tags":[{"name":"python","slug":"python","permalink":"http://www.shelven.com/tags/python/"}]},{"title":"python自学笔记(1)——数据类型","slug":"python自学笔记(1)——数据类型","date":"2022-11-23T14:09:49.000Z","updated":"2022-12-03T16:03:26.000Z","comments":true,"path":"2022/11/23/a.html","link":"","permalink":"http://www.shelven.com/2022/11/23/a.html","excerpt":"这两个月经历了突然的疫情隔离,研究生开题,学术论坛,研究生创新项目等等……终于在这一周尘埃落定了,得以静下心来整理整理自己的一些学习笔记。之前我用过一些python编写的项目,我也只是依葫芦画瓢或者在demo上直接改,还没有系统性地学习过这门编程语言。这里就再记录下自己自学python的一些入门时的笔记,以及记录下几个机器学习方面的python库的使用方法。","text":"这两个月经历了突然的疫情隔离,研究生开题,学术论坛,研究生创新项目等等……终于在这一周尘埃落定了,得以静下心来整理整理自己的一些学习笔记。之前我用过一些python编写的项目,我也只是依葫芦画瓢或者在demo上直接改,还没有系统性地学习过这门编程语言。这里就再记录下自己自学python的一些入门时的笔记,以及记录下几个机器学习方面的python库的使用方法。 1. 六大数据类型很多编程语言的数据类型是相通或者有类似之处的,学习一门编程语言最基础的就是熟悉它的数据类型,python有6种标准数据类型。 1.1 Numbers(数字类型)数字类型简单来说就是数值,在python中是不可变数据类型。python的Numbers数据类型又可以分为以下几个子类型 整型(int): 通常称为整型或整数,是正或负整数,不带小数点。python3整型没有大小限制,可以当作python2的Long类型使用,不像其他编程语言有 int,smallint,short,long,longint,long 等。 浮点型(float): 浮点型由整数和小数两个部分组成,只能以十进制表示或者科学计数法表示,有长度限制。 布尔型(bool): 布尔型就是逻辑,使用True和False表示。注意一下在上下文环境中,True当做1,False被当作0。 复数型(complex): 复数型由实数和虚数部分构成,可以用a + bj或者complex(a, b)表示,a和b都是浮点型。 1.2 String(字符串)String是python中最常用的数据类型,说白了就是字符组成的一串内容。可以使用成对的单引号或者双引号(“或‘)创建字符串,用三个单引号或者双引号使字符串内容保持原样输出,可以包含特殊字符。在python中字符串是不可变变量。 1.2.1 字符串索引索引就是字符的位置序号,使用[]进行字符串索引,python有两种索引方式,下标索引越界均会报错。 正向索引:字符串长度为n,从0开始,索引值范围0 ~ n-1 反向索引:字符串长度为n,从-1开始,索引值范围-1 ~ -n 1.2.2 字符串切片切片意思就是取出字符串中你想要的内容。切片的标准写法是两个冒号加三个数字,如a[1:2:3],需要注意切片是左闭右开的取值,切片越界是不会报错的。 第一个数字表示切片的起始位置(省略就是从第 1 个字符开始,也就是0号位) 第二个数字表示切片的终止位置(不包括这个位置的字符,右开表现在这里;可省略,省略是最后一个字符结尾且包含) 第三个数字表示步长(缺省值为1,此时可以不写第二个冒号) 1.2.3 转义如果使用带有特殊字符的字符串,则需要进行转义,使用反斜杠 \\ 进行字符转义。 转义字符 描述 \\‘ 表示单引号 \\“ 表示双引号 \\n 换行 \\t 制表符(即四个空格) \\b 退格(删除前面一个字符) \\\\ 表示反斜杠 在字符串前加 ’r‘ 可以使整个字符串原样输出,不会被转义。 1.2.4 格式化输出和占位符格式化输出意思是按照格式说明所描述的文字规则进行输出,占位符的使用是格式化输出的表现形式。占位符的意思是替后面的变量占住这个位置,因此所有占位符最后都需要格式化定义占位符的映射(也就是解释占位符代表的东西)。 这里记录一下最常用的占位符 占位符 描述 %s 针对所有数据类型 %d 针对整型数据类型 %f 只针对浮点数 %.xf 浮点数精确到小数点后x位,注意有个点 可以看到两种输出方式得到的结果是一样的,使用占位符进行格式化输出更简介,且更常用。 1.2.5 常用函数python还有很多数据操作的函数,这里记录最常用的几个,以后继续补充 type(): 查看数据类型 len(): 查看字符串长度 int(): 将数据类型转换为整数,如int(“1234”)得到结果整型1234 float(): 转换为浮点数,如float(“12.34”)得到结果浮点型12.34 str(): 转换为字符串,如str(123456)得到结果“123456” 1.3 List(列表)列表数据可以存储任意一种数据类型,是python特有的数据类型,列表用来存储由多个值构成的序列,可以嵌套其他列表,是一种可变数据类型。 不同数据项之间由逗号分开,整体放在一个方括号[]里,就可以创建列表,如ls = [1, 2, 3, 4]就是一个列表。 1.3.1 修改列表元素因为列表是可变数据类型,因此可以用索引或者切片的方法修改列表中的元素。 可以使用del删除列表或者列表中索引为某个数的元素。 1.3.2 列表生成式除了直接创建列表,还可以使用列表生成式直接生成列表。 1.3.3 列表的方法函数记录一下操作列表的常用方法,这里就不演示了。 方法 描述 list.append(obj) 列表末尾添加新的对象 list.count(obj) 返回某个元素在列表中出现的次数 list.extend(seq) 在列表末尾添加另一个列表的所有元素 list.index(obj) 返回第一个匹配的的索引值 list.insert(index, obj) 在指定索引插入对象 list.pop(index) 移除指定索引的值,并返回该值 list.sort() 对原列表进行升序排序(纯数字才可以),降序需要添加reverse=True list.reverse() 反转列表元素 list.remove(obj) 移除第一个匹配的某对象 1.4 Tuple(元组)元组也是python的一种特殊数据类型,和列表很相似,但是是不可变对象。如果想创建一个全局都不变的变量,可以考虑创建元组。 元组中的元素用逗号分隔,一般要使用小括号(小括号不是必须的,只是为了方便理解和美观)。 元组中如果只有一个元素,需要在元素后加逗号。否则无法判断这是一个元组还是一个整型数据。 同样元组数据也可以进行索引和切片,这里不赘述。 1.4.1 元组和列表的互相转化元组转化列表使用list()函数,列表转化元组使用tuple()函数。 1.4.2 结合元组的列表生成式元组不能通过和列表一样的生成式来创建,但是列表生成式中可以加入元组。 1.5 Set(集合)集合是一个无序的不重复元素序列,可以使用大括号{}或者set()函数创建集合,但是创建一个空集合必须要用set()而不是{},因为{}是用来创建一个空字典的。 因为用的不多,简单记录一下,集合有三个特点 集合的元素是无序的。 如:{1, 2, 3}和{1, 3, 2}是完全相等的。 集合的元素是不重复的。 如:{1, 1, 1}只会保留一个值,打印结果为{1}。 集合的元素必须是不可变数据类型(数字、字符串和元组)。 如:{1, [1, 2]}打印结果会报错,因为列表是可变数据类型。 1.6 Dictionary(字典)字典用的比较多,其存储特点是键值对的形式出现(Key-Value),一个键对应一个值,每个键值对用冒号隔开,每对键值对用逗号隔开。字典也可以存储任意类型数据。 需要注意的一点,在字典数据类型中,键必须是唯一的,但是值可以不唯一,值可以取任何数据类型,但是键必须是不可变数据类型。 1.6.1 字典创建和修改字典数据可以通过花括号直接创建,或者通过dict()函数创建(非空)。 通过访问键来访问对应的值,添加、删除和修改的方法均类似。 1.6.2 字典的方法函数记录一下常用的字典方法函数,就不演示了,具体用到的时候可以查。 方法 描述 dict.keys() 返回所有键的列表dict_key对象,可以转换成列表、元组和集合 dict.values() 返回所有值的列表dict_values对象,也可以转换成列表、元组和集合 dict.items() 返回所有键值对的列表dict_items对象,同样可以转换成列表、元组和集合 dict.clear() 清空字典,无返回值,只剩下空字典 dict.get(key, default=None) 返回字典中指定key的value值,如果key不存在,则返回default值 dict.pop(key, default=None) 删除指定的key并返回对应的value值,如果key不存在,则返回default值 简单小结一下关于python数据类型的注意点: 1.可变数据类型:List、Dictionary、Set 2.不可变数据类型:Tuple、Numbers、String。不可变体现在索引这些变量名的元素不可被重新赋值 3.下标索引:String、List、Tuple支持下标索引,Dictionary是通过Key值索引 4.切片:String、List、Tuple支持切片操作","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"编程自学","slug":"编程自学","permalink":"http://www.shelven.com/categories/%E7%BC%96%E7%A8%8B%E8%87%AA%E5%AD%A6/"}],"tags":[{"name":"python","slug":"python","permalink":"http://www.shelven.com/tags/python/"}]},{"title":"深度学习笔记","slug":"深度学习笔记","date":"2022-09-19T16:58:05.000Z","updated":"2022-12-03T16:21:13.000Z","comments":true,"path":"2022/09/20/a.html","link":"","permalink":"http://www.shelven.com/2022/09/20/a.html","excerpt":"开坑记录一下学习人工智能(深度学习为主)的笔记,方便以后回顾学习~整理自点头教育","text":"开坑记录一下学习人工智能(深度学习为主)的笔记,方便以后回顾学习~整理自点头教育 人工智能的趋势展望1. 前沿技术Transformer模型基于自注意力机制,有效提高模型训练效率 由Google的Ashish Vaswani等人和多伦多大学的Aidan N.Gomez于2017年首次提出,是一种基于自注意力机制(在Transformer模型中起基础作用,可减少对外部信息的依赖,更擅长捕捉数据或特征的内部关系,优化模型训练结果)的深度学习模型,该模型主要由编码器和解码器构成,模型本身并行度较高,在精度和性能上均要优于传统的循环神经网络(RNN)和卷积神经网络(CNN)。Transformer模型在简单语言问答和语言建模任务上有着较好表现。 BERT模型基于Transformer Encoder构建的预测模型 由Google于2018年提出,是基于Transformer Encoder构建的一种模型。模型基本思想:给定上下文来预测下一个词。BERT模型架构是由多接口组成的Transformer编码器层,即全连接神经网络增加自注意力机制。对于序列中的每个输入标记,每个接口计算键值和查询向量,相关向量用于创建加权表示,合并同一层中所有接口输出并通过全连接层运行。每个层使用跳跃连接进行包装,之后将层归一化处理。 自监督学习将无监督问题转化为有监督问题的方法 旨在对于无标签数据,通过设计辅助任务来挖掘数据自身的表征特性作为监督信息,来提升模型的特征提取能力,将无监督问题转化为有监督问题的方法。 说到自监督就顺便说下有监督学习和无监督学习,有监督给定的结果是确定的;无监督是实际应用场景中最多的,结果不确定,根据类别未知(没有被标记)的训练样本解决模式识别中的各种问题。 类脑计算模拟大脑结构和信息加工过程,提高机器认知能力、降低运行功耗 类脑计算(Brain-Inspired Computing): 又称神经形态计算,是借鉴生物神经系统信息处理模式和结构的计算理论、体系结构、芯片设计以及应用模型与算法的总称。类脑计算可模拟人类大脑信息处理方式,以极低的功耗对信息进行异步、并行、高速和分布式处理,并具备自主感知、识别和学习等多种能力,是实现通用人工智能的途径之一。 AI大模型包含万亿量级参数的预训练模型,显著降低模型训练成本 AI大模型(Foundation Models):是指经过大规模数据训练且在经微调后即可适应广泛下游任务的模型。随着参数规模不断扩大,AI大模型在语言、视觉、推理、人机交互等领域涌现出新能力。 2. 人工智能的产业融合人工智能与元宇宙元宇宙(Metaverse):本质上是对现实世界的虚拟化、数字化过程,其主要包括基础设施、人机交互、空间计算等七层架构,其中计算机视觉、AI芯片和嵌入式AI等人工智能技术及基础设施共同助力元宇宙加速落地。元宇宙涵盖芯片、云计算、技术平台、通信、智能设备、内容服务等庞大生态系统。 人工智能与生命科学AlphaFold是由谷歌旗下DeepMind团队基于深度学习算法的蛋白质结构预测的人工智能系统,其被视作人工智能深入到生物领域的一大突破。目前AlphaFold已对98.5%的人类蛋白质结构做出预测,此外还对于大肠杆菌、果蝇、斑马鱼、小鼠等研究时常用生物的蛋白质结构进行预测。(这块比较感兴趣,有空继续了解一下) 人工智能与新冠疫情Eva是用于检测入境旅客新冠病毒的强化学习系统,其由美国南加州大学、美国宾夕法尼亚学、AgentRisk以及希腊相关专家合作开发。 2020年,Eva系统被部署到希腊所有入境口岸(机场、港口、车站等),用于识别限制新冠无症状旅客入境。(这里存疑,用算法确定新冠受检者,虽然在一定程度上能缓解新冠检测用品有限的不利情况,但是无疑会漏掉部分入境的可能感染者,一旦感染爆发得不偿失) 人工智能与半导体AI与EDA紧密融合,促使芯片PPA结果更加稳定 为使PPA优化结果更佳,同时为应对芯片安全性需求提升、设计规模攀升及工艺节点微缩等趋势,EDA厂商开始利用AI技术解决半导体芯片设计问题。在EDA中,数据快速提取模型、布局和布线、电路仿真模型、 PPA优化决策等环节均有AI技术参与。 人工智能与碳中和人工智能在预测、监测、优化三大环节赋能碳中和 当前,碳中和已获得全球超过40个国家和地区承诺,其中大部分国家宣布将于2050年左右实现碳中和目标。从整体来看,人工智能将从预测、监测、优化三大环节助力碳中和,如预测未来碳排放量、实时监测碳足迹、优化工作流程等。 人工智能与冬奥会2022年2月,第24届冬季奥林匹克运动会成功在北京举办。人工智能技术在冬奥会开幕式、比赛项目、运动员训练等多个场景实现应用,助力科技冬奥目标实现。 3. 人工智能产业发展的路径探究人工智能在“科研成果—商业化落地”过程中依然存在诸多挑战 伦理与安全人工智能发展面临隐私保护与算法合规使用等方面挑战 随着人工智能技术的高速发展与普及应用,由其产生的伦理与安全问题日益受到关注。人工智能不但延续信息技术的伦理问题,又因深度学习算法具有不透明、难解释、自适应、运用广泛等特征而在基本人权、社会秩序、国家安全等方面产生新问题。 国家间技术限制国家间技术限制阻碍人工智能技术进步 当前,开源深度学习框架、开源工具集、开源应用软件快速发展,国际间AI技术交流不断深入,但部分国家和政府间组织为保持自身AI 技术优势,限制AI技术交流。如美国在2021年6月发布《创新与竞争法案》,在AI、无人机、芯片等多个领域限制与中国合作;美国商务部于2019年10月和2020年5月将商汤科技、科大讯飞等多家中国AI公司加入其实体清单,实施投资限制;2022年白宫修订“关键和新兴技术(CET)清单”,对AI技术具体分类并实行技术封锁。欧盟则于2021年9月通过最新出口管制法规,内容涵盖人脸识别等AI技术。 上述相关政策与未来人工智能发展趋势背道而驰,不利于各国开展技术合作。 深度学习算法部分内容迁移学习将知识由源域迁移至目标域,提高机器学习效率 迁移学习(Transfer Learning,TL):是一种机器学习方法,是把已训练好的模型参数迁移到新的模型来帮助新模型训练,其核心目标是将知识从源域迁移到目标域,让机器也可以做到“触类旁通”。 迁移学习的主要优点是节省模型训练时间,且在目标域训练数据不足时,模型仍能取得较好的性能。 迁移学习的训练框架可以概括为:1)选择源模型,从可用模型中挑选出预训练模型;2)重用模型,在目标域中使用源模型进行训练;3)调整模型。模型可以在目标数据集中对输入-输出进行选择性微调,以让其适应目标任务。 实现迁移学习的方式主要包括样本迁移、特征迁移、模型迁移。目前,迁移学习主要应用在计算机视觉、自然语言处理等领域。 神经网络与卷积神经网络神经网络具有适应性简单单元组成的广泛并行互联网络 神经网络(Neural Network):由数千甚至数百万个紧密互连的简单处理节点组成,其主要包括输入层(输入数据)、中间层/隐藏层(学习复杂决策边界)和输出层(输出结果)。 神经网络可以用于回归,但主要应用于分类问题。如下图所示:输入层表示输入图像(64维向量),中间层使用Sigmoid等非线性函数对于输入层数据进行计算,输出层使用非线性函数对于中间层数据进行计算。 神经网络通过采取设置中间层的方式,利用单一算法学习各种决策边界,调节中间层数量以及层的深度,神经网络可学习更复杂的边界特征,而得出更加准确的结果。 卷积神经网络以图像识别为核心的深度学习算法 卷积神经网络(Convolutional Neural Network,CNN):由数千甚至数百万个紧密互连的简单处理节点组成,其主要包括输入层、卷积层、池化层、全连接层和输出层,适合处理图片、视频等类型数据。 1980年,日本科学家福岛邦彦提出一个包含卷积层、池化层的神经网络结构。在此基础上,Yann Lecun将BP算法应用到该神经网络结构的训练上,形成当代卷积神经网络的雏形;1988年,Wei Zhang提出第一个二维卷积神经网络:平移不变人工神经网络(SIANN),并将其应用于检测医学影像;1998年Yann LeCun及其合作者构建了更加完备的卷积神经网络LeNet-5并在手写数字的识别问题中取得成功。 卷积层:图片输入转化成RGB对应的数字,然后通过卷积核做卷积,目的是提取输入中的主要特征,卷积层中使用同一卷积核对每个输入样本进行卷积操作; 池化层:作用在于减小卷积层产生的特征图尺寸(压缩特征映射图尺寸有助于降低后续网络处理的负载); 全连接层:计算激活值然后通过激活函数计算各单元输出值(激活函数包括Sigmoid、tanh、ReLU等) 输出层:使用似然函数计算各类别似然概率。 循环神经网络与图神经网络循环神经网络用于处理序列数据的神经网络 循环神经网络(Recurrent Neural Network,RNN):是一类以序列数据(指相互依赖的数据流,比如时间序列数据、信息性的字符串、对话等)为输入,在序列的演进方向进行递归且所有节点(循环单元)按链式连接的神经网络。目前,语言建模和文本生成、机器翻译、语音识别、生成图像描述、视频标记是RNN应用最多的领域。 图神经网络用于处理图结构数据的神经网络 图神经网络(Graph Neural Networks,GNN):将图数据和神经网络进行结合,在图数据上面进行端对端的计算,具备端对端学习、擅长推理、可解释性强的特点。 图神经网络发展出多个分支,主要包括图卷积网络、图注意力网络、图自编码器、图生成网络和图时空网络等。 图神经网络的训练框架如下:首先,每个节点获取其相邻节点的所有特征信息,将聚合函数(如求和或取平均)应用于这些信息。 聚合函数的选择必须不受节点顺序和排列的影响。之后,将前一步得到的向量传入一个神经网络层(通常是乘以某个矩阵),然后使用非线性激活函数(如ReLU)来获得新的向量表示。 目前,图神经网络在许多领域的实际应用中都展现出强大的表达能力和预测能力,如物理仿真、科学研究、生物医药、金融风控等。 长短期记忆神经网络在RNN中加入门控机制,解决梯度消失问题 长短期记忆神经网络(Long Short-Term Memory,LSTM):LSTM是一种特殊的循环神经网络(RNN)。传统RNN在训练中,随着训练时间的加长和层数的增多,很容易出现梯度爆炸或梯度消失问题,导致无法处理长序列数据,LSTM可有效解决传统RNN“长期依赖”问题。 LSTM由状态单元、输入门(决定当前时刻网络的输入数据有多少需要保存到单元状态)、遗忘门(决定上一时刻的单元状态有多少需要保留到当前时刻)、输出门(控制当前单元状态有多少需要输出到当前输出值)组成,以此令长期记忆与短期记忆相结合,达到序列学习的目的 LSTM应用领域主要包括文本生成、机器翻译、语音识别、生成图像描述和视频标记等。(我前一篇博客做的tts用了Tacotron2,其编码器模块中就引入了一个双向LSTM层) 自编码器通过期望输出等同于输入样本的过程,实现对输入样本抽象特征学习 典型深度无监督学习模型包括自编码器、受限波尔兹曼机与生成对抗网络。 自编码器(Autoencoder,AE):包括编码器和解码器两部分,其中编码器将高维输入样本映射到低维抽象表示,实现样本压缩与降维;解码器将抽象表示转换为期望输出,实现输入样本的复现。自码器的输入与期望输出均为无标签样本,隐藏层输出则作为样本的抽象特征表示。 自编码器仅通过最小化输入样本与重构样本之间的误差来获取输入样本的抽象特征表示,无法保证自编码器提取到样本的本质特征。为避免上述问题,需要对自编码器添加约束或修改网络结构,进而产生稀疏自编码器、去噪自编码器、收缩自编码器等改进算法。 自编码器凭借其优异的特征提取能力,主要应用于目标识别、文本分类、图像重建等诸多领域。 生成对抗网络生成对抗网络(Generative Adversarial Network,GAN):通过使用对抗训练机制对两个神经网络进行训练,避免反复应用马尔可夫链学习机制带来的配分函数计算,明显提高应用效率。 生成对抗网络包含一组相互对抗模型—判别器和生成器,判别器目的是正确区分真实数据和生成数据,使得判别准确率最大化,生成器是尽可能逼近真实数据的潜在分布。生成器类似于造假钞的人,其制造出以假乱真的假钞,判别器类似于警察,尽可能鉴别出假钞,最终造假钞的人和警察双方在博弈中不断提升各自能力。(同样是我前面一篇博客语音合成tts中,应用的HiFiGAN就是基于GAN的声码器)","categories":[{"name":"深度学习","slug":"深度学习","permalink":"http://www.shelven.com/categories/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"人工智能","slug":"人工智能","permalink":"http://www.shelven.com/tags/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/"}]},{"title":"go-cqhttp扫码登录异常的解决方法","slug":"go-cqhttp扫码登录异常的解决方法","date":"2022-09-09T11:43:05.000Z","updated":"2023-08-04T15:08:20.000Z","comments":true,"path":"2022/09/09/b.html","link":"","permalink":"http://www.shelven.com/2022/09/09/b.html","excerpt":"假期用自己的服务器搭建了一个基于 Nonebot2 和 go-cqhttp 框架的QQ聊天机器人,使用的开源项目是绪山真寻bot(项目地址点击这里)。因为项目提供了一键安装包,这里就不详细说安装过程了,简单说下首次运行或者切换bot QQ号会碰到的go-cqhttp扫码登陆异常的问题。","text":"假期用自己的服务器搭建了一个基于 Nonebot2 和 go-cqhttp 框架的QQ聊天机器人,使用的开源项目是绪山真寻bot(项目地址点击这里)。因为项目提供了一键安装包,这里就不详细说安装过程了,简单说下首次运行或者切换bot QQ号会碰到的go-cqhttp扫码登陆异常的问题。 注:写这篇博客的时候go-cqhttp版本为1.0.0,往后的版本已适配签名服务器,本方法已不再适用,详情请看[go-cqhttp登录异常(错误码45)的解决办法](https://www.shelven.com/2023/08/04/a.html) 问题描述:首次运行或者切换bot QQ号后,go-cqhttp会要求需要登录验证,由于纯linux系统无法使用浏览器抓取滑条,因此会自动跳转到手机QQ扫码验证。 但是扫码会提示两个设备不在一个网络,无法登录。(很明显我的云端linux服务器不可能和手机能在一个网络中) 这个问题是腾讯QQ安全机制引起的,很明显是限制QQ机器人的手段,也就是你扫码的网络环境要和服务器的网络环境一致才可以登录。 解决方法:第一步 下载和运行win版go-cqhttp项目下载地址Releases · Mrs4s/go-cqhttp (github.com) 选择下载最新版本的go-cqhttp_windows_amd64,解压后有三个文件 双击exe文件,提示要在power shell中运行,确认,自动生成go-cqhttp.bat的批处理文件 双击运行go-cqhttp.bat,选择013,回车 修改生成的config.yml配置文件(主要就是改bot QQ号和密码) 修改之后再次运行go-cqhttp.bat,看到连接成功,网络没有问题即可 前面的反向代理失败统统不用管(因为我没有设置),我们只需要win版go-cqhttp提供设备登录信息文件(device.json)和密钥信息文件(session.token)即可。这两个文件特别重要,尤其是device.json,缺一个都将会导致登陆失败。 第二步 替换文件替换linux服务器go-cqhttp文件夹下的device.json和session.token(有的话替换,无的话直接加进去)文件,config文件最好不要替换,你只要改一下qq号和密码就行,防止底下设置的反向连接端口出错(很重要!!)。 重新在linux上启动go-cqhttp,问题解决。","categories":[{"name":"QQ机器人","slug":"QQ机器人","permalink":"http://www.shelven.com/categories/QQ%E6%9C%BA%E5%99%A8%E4%BA%BA/"}],"tags":[{"name":"qq bot","slug":"qq-bot","permalink":"http://www.shelven.com/tags/qq-bot/"},{"name":"go-cqhttp","slug":"go-cqhttp","permalink":"http://www.shelven.com/tags/go-cqhttp/"}]},{"title":"深度学习——语音合成tts笔记(下)","slug":"深度学习——语音合成tts笔记(下)","date":"2022-09-08T19:32:17.000Z","updated":"2022-12-03T16:20:43.000Z","comments":true,"path":"2022/09/09/a.html","link":"","permalink":"http://www.shelven.com/2022/09/09/a.html","excerpt":"(接上篇博客)做完数据集,我们手里现在有训练集(train set)和测试集(test set)音频文件,以及对应的拼音文本,接下来就可以跑Tacotron2来训练模型了。","text":"(接上篇博客)做完数据集,我们手里现在有训练集(train set)和测试集(test set)音频文件,以及对应的拼音文本,接下来就可以跑Tacotron2来训练模型了。 2. 训练模型 & 合成语音Tacotron2项目地址点击这里 HiFi-GAN项目地址点击这里 本篇博客训练模型&合成语音基于以上两个开源项目,再次感谢原作者! 2.1 Tacotron2简介简单讲一讲Tacotron2,它是由google推出的从文本中合成语音的神经网络结构,也就是一个语音合成(Text To Speech,TTS)框架,可以实现端到端的语音合成。Tacotron2与其前代Tacotron类似,比较重要的一个区别是在编码器模块中引入了一个双向LSTM层和卷积层,相比原来的CBHG堆叠结构和GRU循环层更为简洁。 模型主要由两部分组成: 声谱预测网络:特征预测网络,包含一个编码器和一个引入注意力机制(attention)的解码器,作用是将输入字符序列预测为梅尔频谱的帧序列。 声码器(vocoder):将预测的梅尔频谱帧序列转换产生时域波形样本,算是WaveNet的修订版。 原项目中的声码器我们暂时不用(上面地址提供的Tacotron 2就是没有wavenet的版本),因为有更好的工具HiFi-GAN。 代码实现详解有很多博客可以参考((16条消息) Tacotron2 论文 + 代码详解_HJ_彼岸的博客-CSDN博客_tacotron2),这里只要知道我们是用Tacotron2生成梅尔频谱,在此基础上结合我们输入的字符序列(也就是对应的拼音文本)训练模型。 特别注意一点:Tacotron 2是基于tensorflow1.5版本运行的,如果是自己电脑上配置环境的话,务必将python版本降到3.7以下!否则将会无法安装tensorflow1.5,除了tensorflow有硬性版本要求之外,其他依赖都可以安装最新版本——反复配置环境治好了我的精神内耗 如果你不想和我一样配置好几天环境的话,我推荐最好使用google colab,一键解决环境问题,下面会说到。 2.2 HiFi-GAN简介简单说下,声码器的作用就是将梅尔频谱转换成语音信号,和上面是对应的。 为什么我们没有用上面Tacotron2的声码器呢,主要原因就是现在有很多更优秀的声码器供我们选择。 早期比较有名的声码器WaveNet,它是一种自回归卷积神经网络,合成的效果非常好可以说和人类发声非常相似,但有个致命的缺点——合成速度太慢。直到2020年项目作者开发了这套基于GAN(生成式对抗网络)的神经网络声码器,从作者的论文里可以找到,HiFi-GAN在GPU上可以以比实时速度快167.9倍的速度生成22.05 kHz的语音,在CPU上可以以比自回归模型快13.4倍的速度生成语音,这就是它的牛逼之处。 HiFi-GAN主要有一个生成器和两个判别器,具体结构就不说了,知道一下生成器和两个判别器是通过对抗学习的方法训练的,新增加了两个损失函数来提高训练的稳定性和提高模型的性能。有能力的小伙伴可以看原论文(HiFi-GAN: Generative Adversarial Networks for Efficient and High Fidelity Speech Synthesis)了解详情。 需要注意一下作者使用VCTK数据集进行实验,测试了3个模型(V1、V2和V3),简单来说V1是最优模型,作者发布的预训练模型以及相应的配置文件都是以V1模型为基础的。我在这篇博客使用的HiFi-GAN模型g_02500000就是作者的预训练模型,配置文件为config.json。 HiFi-GAN预训练模型与配置文件下载地址: UNIVERSAL_V1 - Google 云端硬盘 2.2 注册谷歌colab和谷歌云盘训练模型是一件非常消耗算力的过程,因为涉及到图形处理,我们要用GPU进行加速。我的笔记本GPU非常拉跨,跑模型立马爆显存,因此我个人比较推荐白嫖谷歌colab上面的免费专业级GPU(Nvidia K80),免费用户只能用这一种GPU,至少比我的笔记本好多了。 需要注意下colab最大连接时长是12小时,12小时后会强行关闭GPU连接,因此需要注意下你是什么时候开始用GPU跑模型的,并且及时保存数据。关闭后要等待24小时才可以继续使用GPU,所以理论上可以用三个号不间断白嫖GPU资源(我特地申请了4个谷歌号),你只需要偶尔切换屏幕看下是否有谷歌的人机验证就行。 这里为什么还推荐谷歌云盘呢,是因为谷歌云盘可以挂载到colab上,这样调用文件就非常方便,及时保存不用担心数据丢失。谷歌云盘提供15GB的免费空间,如果保存模型比较频繁的话可能不够用,但是我们可以申请无限量的团队盘(共享云端硬盘)薅羊毛必备。 2.3 使用colab训练模型 & 合成语音我使用的colab笔记文件因为时间久远已经找不到出处了(后续如果找到会标注出来,向原作者致谢!),为了跑中文语音模型,自己也修改了很多参数和步骤,一一解释过于麻烦了….感兴趣的小伙伴可以看笔记文件。具体操作流程在底下的视频(或者点击此处看我的B站视频)。 Your browser does not support the video tag. 所有工程文件和资源如下: Tacotron2+HiFiGAN打包 链接:https://pan.baidu.com/s/1ngCUvifQM6ETwuG-NFQeGA 提取码:z6h3 400条派蒙语音测试集 链接:https://pan.baidu.com/s/1g0C0Ck4P_BxdTgMirKRc-g 提取码:5ew1 1800条派蒙语音训练集 链接:https://pan.baidu.com/s/1IDA4lppAJGHnophQAwkxNg 提取码:f2xk 需要提及一点,colab在2022年8月1号之后不再支持tensorflow1.5,请教大佬之后我将Tacotron2项目下超参数配置hparams.py改成如下即可正常运行: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788import tensorflow as tffrom text import symbolsclass hparams: def __init__(self) -> None: super().__init__() ################################ # Experiment Parameters # ################################ epochs = 3 #500 iters_per_checkpoint = 1000 seed = 1234 dynamic_loss_scaling = True fp16_run = False distributed_run = False dist_backend = "nccl" dist_url = "tcp://localhost:54321" cudnn_enabled = True cudnn_benchmark = True ignore_layers = ['embedding.weight'] ################################ # Data Parameters # ################################ load_mel_from_disk = False #实际上是区别用 numpy读wav ,还是用scipy读wav training_files = 'filelists/zh_audio_text_train_filelist.txt' validation_files = 'filelists/zh_audio_text_val_filelist.txt' text_cleaners = ['english_cleaners'] ################################ # Audio Parameters # ################################ max_wav_value = 32768.0 sampling_rate = 22050 #22050 filter_length = 1024 hop_length = 256 win_length = 1024 n_mel_channels = 80 mel_fmin = 0.0 mel_fmax = 8000.0 ################################ # Model Parameters # ################################ n_symbols = len(symbols) symbols_embedding_dim = 512 # Encoder parameters encoder_kernel_size = 5 encoder_n_convolutions = 3 encoder_embedding_dim = 512 # Decoder parameters n_frames_per_step = 1 # currently only 1 is supported decoder_rnn_dim = 1024 prenet_dim = 256 max_decoder_steps = 1000 gate_threshold = 0.5 p_attention_dropout = 0.1 p_decoder_dropout = 0.1 # Attention parameters attention_rnn_dim = 1024 attention_dim = 128 # Location Layer parameters attention_location_n_filters = 32 attention_location_kernel_size = 31 # Mel-post processing network parameters postnet_embedding_dim = 512 postnet_kernel_size = 5 postnet_n_convolutions = 5 ################################ # Optimization Hyperparameters # ################################ use_saved_learning_rate = False learning_rate = 1e-3 weight_decay = 1e-6 grad_clip_thresh = 1.0 batch_size = 2 #64 mask_padding = True # set model's padded outputs to padded valuesdef create_hparams(hparams_string=None, verbose=False): return hparams 2.4 注意事项 训练的epoch不是越多越好,我个人经验epoch 超过400会发生过拟合,测试集loss会越来越大,当然这和数据集有着密切的关系。过拟合具体表现为合成语音有部分字无法发音。 每个epoch自动保存模型且会覆盖谷歌云盘的原文件,因此务必要隔一段时间保存到本地,以免错过最佳模型(或者你改代码,比如50 epoch保存一次)。 对于文本的处理,需要参考Tacotron 2项目下的text文件夹中的四个文件cleaners.py、cmudict.py、numbers.py和symbols.py,我是进行了最简单的设置,可以根据自己需要更改。 如果你原封不动用的我的工程文件,想在本地运行合成语音的推理程序,务必将cleaners选择english_cleaner(否则会出现古神的低语)。 如果你是自己训练模型,个人认为筛选数据集非常重要,尽量把语气词和背景噪音去掉,否则效果会很差。 训练模型的参数可以根据GPU自行调整,batch_size是影响训练速度最大的因素,当你不确定显卡性能如何,请务必确保运行一段时间后显存没有炸(我就是运行以后直接睡觉了,醒来发现显存在运行半小时的时候炸了,我心态也炸了) 其实这个模型效果仍然不是很让我满意,有电音的问题可以用HiFi-GAN再训练过滤一下,我是直接用的官方预训练模型,因此效果会差一点。由于现在开学了要忙着搞开题,最近也没时间再优化模型了,以后有想法会继续补充。 我自己有考虑过将模型传到服务器,用服务器cpu运行推理,摆脱colab的限制,但是服务器不堪重负…一运行推理运存就炸…github上有不少前人做过纯cpu推理的GUI(比如MoeTTS),亲测可行。 哦对了,我在做这个项目的时候,发现已经有人基于VITS做了同个游戏的端到端语音合成,甚至开发公布了API…不得不感慨这些大佬真的用心了,有API就意味着有更多的使用方式。我搭了个顺风车,通过搭建QQ机器人,写了个原神语音合成插件,效果是可以指定原神任何角色合成任意想说的语音并且发在QQ群里(没有什么技术含量,内行看个笑话),有空尽量更新出来吧! 2022/9/10更新 已将插件更新至我的github仓库,地址Phantom-Aria/zhenxun_plugin_tts: 真寻bot插件,原神角色语音合成tts (github.com) 由于代码写的比较幼稚,就不申请官方插件索引了 适配绪山真寻bot 功能:指定某原神角色合成想要说的话 指令:[角色名]说/说过[文本] 2022/10/7更新由于原API已下线,此插件不再生效,后续再更新","categories":[{"name":"深度学习","slug":"深度学习","permalink":"http://www.shelven.com/categories/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"google colab","slug":"google-colab","permalink":"http://www.shelven.com/tags/google-colab/"},{"name":"Tacotron2","slug":"Tacotron2","permalink":"http://www.shelven.com/tags/Tacotron2/"},{"name":"HiFiGAN","slug":"HiFiGAN","permalink":"http://www.shelven.com/tags/HiFiGAN/"}]},{"title":"深度学习——语音合成tts笔记(上)","slug":"深度学习——语音合成tts笔记(上)","date":"2022-09-07T19:15:04.000Z","updated":"2022-12-03T16:20:24.000Z","comments":true,"path":"2022/09/08/a.html","link":"","permalink":"http://www.shelven.com/2022/09/08/a.html","excerpt":"两个月的暑假已经结束了,假期里自学了一点深度学习的内容,很多地方还是一知半解的,这里稍微记录一下。之前刷到一个很有意思的语音合成视频,抱着试试看的心态想自己做一个模型,于是给自己挖了一个大坑……涉及到深度学习的知识我还需要慢慢学,因此本篇博客重点还是记录下自己的踩坑操作原理部分以后搞明白了再更新","text":"两个月的暑假已经结束了,假期里自学了一点深度学习的内容,很多地方还是一知半解的,这里稍微记录一下。之前刷到一个很有意思的语音合成视频,抱着试试看的心态想自己做一个模型,于是给自己挖了一个大坑……涉及到深度学习的知识我还需要慢慢学,因此本篇博客重点还是记录下自己的踩坑操作原理部分以后搞明白了再更新 总的来说,我通过拆包游戏客户端获得5.6万条语音文件,通过github上的一个声纹识别项目分离其中一个角色的语音文件。接着用百度的语音识别API将语音识别为文本后,人工校正一遍文本,然后转换为拼音+音标,以此制作语音数据训练集和测试集。基于开源项目Tacotron2训练角色语音模型,经历400 epoch后初步训练成型,最后基于HiFiGAN合成语音。整个后半段流程是在google colab上完成的,为了完成模型训练我申请了4个谷歌账号…不得不说白嫖的GPU真香~ 1. 制作数据集可以说整个项目大部分时间花费在整理数据集上,根据我自己的经验,数据集的语音长度在2秒-10秒之间效果最好,数量大约在2000条左右(为了涵盖尽可能多的汉字发音)。需要注意一点,不管拆包的原语音采样率如何,都要统一重采样到22050 hz,这是Tacotron2训练模型的要求。 1.1 Extractor2.5 + vgmstream-win拆包首先是这款国内游戏的拆包,所有角色的语音文件都在目录D:\\Genshin Impact\\Genshin Impact Game\\YuanShen_Data\\StreamingAssets\\Audio\\GeneratedSoundBanks\\Windows\\Chinese下,我们使用软件Extractor2.5进行音频文件拆包。 Extractor2.5是个非常好用的游戏解包工具,我们将所有pck源文件所在目录输进去(可以批量选中文件),确定输出目录,点击开始即可。 运行结束之后可以看到这个游戏拆包有56958条语音文件…点击左下角反选,全部解压到自己的文件夹中。 但是你会发现解压出来的wav文件无法打开,需要使用vgmstream进行解密和转码(项目地址戳这里)。 可以看到vgmstream-win文件夹只有一个可执行程序test.exe,其他都是dll库文件。 这个test.exe是不能直接运行的,需要把程序拖到刚才拆包的语音文件上,但是几万条语音我们不可能一个个拖过去,因此我们在语音的文件夹下,写一个如下的批处理文件(命名为批处理.bat),运行批处理就可以了。 12345@echo offfor /r %%i in (*.wav) do ( "D:\\zhuomian\\vgmstream-win\\test.exe" "%%~nxi" #路径改成你自己的,注意路径不能有中文)pause 运行后生成的wav.wav文件就可以正常播放了,所有音频采样率均为48000Hz(采样率很重要,贯穿整个项目)。 1.2 基于Tensorflow的声纹识别这部分内容来源于github(项目地址戳这里),作者基于tensorflow做了个声纹识别模型,通过把语音数据转换短时傅里叶变换的幅度谱,使用librosa计算音频的特征,以此来训练、评估模型。因为我只用到了对比部分,因此我下载了作者预训练的模型,以及对声纹对比文件infer_contrast.py做了修改。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162import argparseimport functoolsimport numpy as npimport tensorflow as tffrom utils.reader import load_audiofrom utils.utility import add_arguments, print_argumentsimport os,shutilimport gcos.environ['TF_CPP_MIN_LOG_LEVEL']='2'parser = argparse.ArgumentParser(description=__doc__)add_arg = functools.partial(add_arguments, argparser=parser)add_arg('audio_path1', str, 'audio_db/Paimon.wav', '标准的派蒙音频') # 自己准备的标准音频,下面两个也是add_arg('audio_path2', str, 'audio_db/Klee.wav', '标准的可莉音频')add_arg('audio_path3', str, 'audio_db/Kokomi.wav', '标准的心海音频')add_arg('input_shape', str, '(257, 257, 1)', '数据输入的形状')add_arg('threshold', float, 0.8, '判断是否为同一个人的阈值')add_arg('model_path', str, 'models1/infer_model.h5', '预测模型的路径') # 作者的预训练模型args = parser.parse_args()# 加载模型model = tf.keras.models.load_model(args.model_path,compile=False)model = tf.keras.models.Model(inputs=model.input, outputs=model.get_layer('batch_normalization').output)# 数据输入的形状input_shape = eval(args.input_shape)# 预测音频def infer(audio_path): data = load_audio(audio_path, mode='test', spec_len=input_shape[1]) data = data[np.newaxis, :] feature = model.predict(data) return featureif __name__ == '__main__': # 预测的两个音频文件 feature1 = infer(args.audio_path1)[0] feature2 = infer(args.audio_path2)[0] feature3 = infer(args.audio_path3)[0] datapath = "./test2" #上传到集群的解包音频文件位置 dirs = os.listdir(datapath) for audio in dirs: personx = 'test2/%s' % (audio) featurex = infer(personx)[0] # 对角余弦值 dist1 = np.dot(feature1, featurex) / (np.linalg.norm(feature1) * np.linalg.norm(featurex)) if dist1 > args.threshold: print("%s 符合派蒙模型,相似度为:%f" % (personx, dist1)) shutil.move("./test2/%s" % (audio),"./dataset/Paimon") # 移动音频文件,路径自选 else: dist2 = np.dot(feature2, featurex) / (np.linalg.norm(feature2) * np.linalg.norm(featurex)) if dist2 > args.threshold: print("%s 符合可莉模型,相似度为:%f" % (personx, dist2)) shutil.move("./test2/%s" % (audio),"./dataset/Klee") else: dist3 = np.dot(feature3, featurex) / (np.linalg.norm(feature3) * np.linalg.norm(featurex)) if dist3 > args.threshold: print("%s 符合心海模型,相似度为:%f" % (personx, dist3)) shutil.move("./test2/%s" % (audio),"./dataset/Kokomi") gc.collect() 需要注意一点,为了提高识别的准确性,这个项目要求的语音长度不能低于1.7s,因此我用ffmpeg将所有长度低于2s的短音频全部过滤了(这里不赘述实现过程)。 之后将三个角色的标准语音分别放在audio_db文件夹下,识别的原理是通过预测函数提取三个角色的音频特征值,对5.6万条音频分别比对三个角色的标准音频特征,求对角余弦值,在多次试验后选择了对角余弦值0.8,作为判断两条语音是否为同一个人的阈值。 直接在集群上运行infer_contrast.py,相似度高于0.8的音频则会被挑选到对应的dataset文件夹中。 实际上这个声纹识别的结果仅能作为参考,不能保证百分百正确,原因有很多: 1.声优都是怪物,一个人用好多相似的声线配了不同角色,导致无法分辨出不同角色的语音(假阳性)。 2.一句话的语调不同会表现出音频特征值不同,而这个算法下会导致对角余弦值偏小,从而判断成发声的是不同的人(假阴性)。 因此识别的结果需要进行人工校正,也就是需要自己听一遍到底是不是这个角色的语音= =(最好同下一步一起进行,省时间) 这里我验证并分离出2293条长度2秒以上的派蒙语音,以其中的1820条作为训练集,473条作为测试集。后续训练模型用到的时候会说。 1.3 基于百度语音识别API的语音转文本光有语音还不行,我们要训练模型就要有对应的文本。很多单机游戏(比如柚子社的游戏)有解包脚本,可以完整解出所有资源,其中就包括语音文件和对应的文本。但是解包有客户端的游戏不同,比如这款游戏发布不同版本的客户端,文件结构就会发生很大的改变,导致以前做的文件定位统统失效,而且包括文本在内的很多文件也是加密的,无法解出(也可能是我个人问题)。 因此,我们还是需要借助语音识别的软件将语音转成文本。这里涉及到另一个问题,不管多么强大的语音转文字技术,都是在已有的数据集基础上不断训练模型而产生的,游戏中有相当多新造的词(比如中二台词,游戏人名,地点等等),这在转化文本过程中是肯定无法百分百准确的,甚至会“空耳”产生歧义。 因此转文本这一步结束后需要人工校准,至少保证读音正确。 我是在百度AI开放平台申请了语音识别API,每个账号有200万次免费调用额度,但是限制并发数2(没办法,既然是白嫖就忍忍)。 查看官方放在github上的demo,改一改就可以调用API了(每当问我不会使用的时候都是看demo然后魔改2333)。 我这里以官网提供的asr_raw.py为例,直接下载,并修改成如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112# coding=utf-8import sysimport jsonimport timeimport gcimport osimport timeIS_PY3 = sys.version_info.major == 3 # 判断你用的是python3.x还是2.x版本,推荐还是用3.xif IS_PY3: from urllib.request import urlopen from urllib.request import Request from urllib.error import URLError from urllib.parse import urlencode timer = time.perf_counterelse: import urllib2 from urllib2 import urlopen from urllib2 import Request from urllib2 import URLError from urllib import urlencode if sys.platform == "win32": timer = time.clock else: # On most other platforms the best timer is time.time() timer = time.timeAPI_KEY = 'XXXXXXXX' # 改成你自己的,下面一条一样SECRET_KEY = 'XXXXXXXX' FORMAT = "wav"; # 文件后缀只支持 pcm/wav/amr 格式CUID = '123456PYTHON';RATE = 16000; # 固定值,这里一定一定要注意采样率DEV_PID = 1537; # 1537 表示识别普通话,使用输入法模型。根据文档填写PID,选择语言及识别模型ASR_URL = 'http://vop.baidu.com/server_api'SCOPE = 'audio_voice_assistant_get' # 有此scope表示有asr能力,没有请在网页里勾选,非常旧的应用可能没有class DemoError(Exception): pass""" TOKEN start """TOKEN_URL = 'http://aip.baidubce.com/oauth/2.0/token'# 核对tokendef fetch_token(): params = {'grant_type': 'client_credentials', 'client_id': API_KEY, 'client_secret': SECRET_KEY} post_data = urlencode(params) if (IS_PY3): post_data = post_data.encode('utf-8') req = Request(TOKEN_URL, post_data) try: f = urlopen(req) result_str = f.read() except URLError as err: print('token http response http code : ' + str(err.code)) result_str = err.read() if (IS_PY3): result_str = result_str.decode() result = json.loads(result_str) if ('access_token' in result.keys() and 'scope' in result.keys()): if SCOPE and (not SCOPE in result['scope'].split(' ')): # SCOPE = False 忽略检查 raise DemoError('scope is not correct') return result['access_token'] else: raise DemoError('MAYBE API_KEY or SECRET_KEY not correct: access_token or scope not found in token response')""" TOKEN end """if __name__ == '__main__': token = fetch_token() """ httpHandler = urllib2.HTTPHandler(debuglevel=1) opener = urllib2.build_opener(httpHandler) urllib2.install_opener(opener) """ for audio in range(1,1825): AUDIO_FILE = str('/public/home/wlxie/test4voice/baiduyun/training_16K/train' + str(audio) + '.wav') #路径改成自己的 speech_data = [] with open(AUDIO_FILE, 'rb') as speech_file: speech_data = speech_file.read() length = len(speech_data) if length == 0: raise DemoError('file %s length read 0 bytes' % AUDIO_FILE) params = {'cuid': CUID, 'token': token, 'dev_pid': DEV_PID} params_query = urlencode(params); headers = { 'Content-Type': 'audio/' + FORMAT + '; rate=' + str(RATE), 'Content-Length': length } url = ASR_URL + "?" + params_query req = Request(ASR_URL + "?" + params_query, speech_data, headers) try: begin = timer() f = urlopen(req) result_str = f.read() except URLError as err: print('asr http response http code : ' + str(err.code)) result_str = err.read() #输出转文字结果 result_str = result_str.decode() result = json.loads(result_str) res = result['result'][0] print('train' +str(audio) + '.wav' + '识别结果:' + res) with open("training_1800_result.txt", "a") as of: of.write('train' + str(audio) + '.wav' + "|" + res + '\\n') # 转成“路径|文本”的格式,方便人工校准 gc.collect() 这里也有一个大坑,这个语音转文本API要求音源采样率必须是16000Hz,前面说到我们解包得到的音频是48000Hz,而且后面训练模型要求采样率为22050Hz!也就是说如果我们现在把所有音频转成16000Hz的话,势必会对训练模型产生影响(高频可以转低频,但是低频转高频语音质量不会有一丁点儿的提升),因此我这边用拆包音频做了两个备份,一个是转成16000Hz,放在training_16K文件下,专门用于语音转文本;一个是转成22050Hz,放在training_22K文件下,专门用于后续训练模型。重采样仍然用我们的老朋友ffmpeg,因为就一行命令的事这里也不赘述了。 前面也说到这个API并发数限制为2,经常是用着用着就断开了(也是我比较笨比,不会写限制并发数发送请求的代码),所以我将训练集的1825个语音写了个小脚本,重命名为train1.wav-train1825.wav,所以才用了for循环一句一句调用API转文本,到哪个地方断了也可以迅速找出来并继续。 总之效果如下,训练集1825条语音和测试集473条语音全部转换为文本,且能清晰地看到一一对应关系: 一眼看效果还不错,为了保证准确率,将txt文件传回本地,人工校正吧(语气词部分本来是要去除的,但是工作量会比较大放弃了,起码要保证发音没问题)。 这个数据集因为不是标准的普通话数据集(标准数据集可以找标贝,就有那种纯合成的标准普通话),声优也有特殊的口癖和发音,额,这是无法避免的。 1.4 基于pypinyin的汉字转拼音因为后面训练模型的Tacotron2是基于英文模型开发出来的,我们无法直接用中文文本训练。一个行之有效的方法是将中文转换成拼音+数字声调的方式,这样数据就可以顺利地被载入。 这里推荐一下pypinyin模块,该模块安装比较方便(直接用pip),也是个非常实用和高质量的汉字拼音转换工具! 我将人工校准后的txt文件传回集群,去掉前面的“|”之前的内容,再写个小脚本将所有标点符号删除,接着汉字转拼音,这里就记录下pypinyin的用法吧。 123456789from pypinyin import lazy_pinyin, Styleimport linecacheoutput_file = open("/public/home/wlxie/test4voice/baiduyun/training_pinyin.txt","w")readlist = list(range(1,1821)) # 人工校准的时候去掉了4条不是该角色的音频for i in readlist: text = linecache.getline("/public/home/wlxie/test4voice/baiduyun/cheat_training.txt",i) text = " ".join(lazy_pinyin(text, style=Style.TONE3)) output_file.write(text) 然后将拼音前按照Tacotron2训练的要求,加上了音频文件对应的colab路径(为什么用这个路径我下一篇博客再说明),以及每句话末尾加个英文的句号,最后输出结果如下: 同样的方法对测试集也转拼音,这样前期的数据集文件就制作完成啦!接下来就是重点——训练模型。下篇博客接着说完。","categories":[{"name":"深度学习","slug":"深度学习","permalink":"http://www.shelven.com/categories/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"拆包","slug":"拆包","permalink":"http://www.shelven.com/tags/%E6%8B%86%E5%8C%85/"},{"name":"声纹识别","slug":"声纹识别","permalink":"http://www.shelven.com/tags/%E5%A3%B0%E7%BA%B9%E8%AF%86%E5%88%AB/"},{"name":"语音转文本","slug":"语音转文本","permalink":"http://www.shelven.com/tags/%E8%AF%AD%E9%9F%B3%E8%BD%AC%E6%96%87%E6%9C%AC/"}]},{"title":"frp内网穿透配置笔记","slug":"frp内网穿透配置笔记","date":"2022-07-12T19:55:04.000Z","updated":"2022-12-03T16:11:04.000Z","comments":true,"path":"2022/07/13/a.html","link":"","permalink":"http://www.shelven.com/2022/07/13/a.html","excerpt":"过一段时间要到校外学习,而学校的资源只能在校园内网下才能使用(登录集群可以看到登录ip是10开头的A类地址,无法公网ip访问)。为了方便在校外访问校园内网的集群,我手里正好也有一个备案过的服务器和域名,于是自己用frp搭建了一个反向代理服务器,穿透了校园内网,这里记录下自己搭建过程。","text":"过一段时间要到校外学习,而学校的资源只能在校园内网下才能使用(登录集群可以看到登录ip是10开头的A类地址,无法公网ip访问)。为了方便在校外访问校园内网的集群,我手里正好也有一个备案过的服务器和域名,于是自己用frp搭建了一个反向代理服务器,穿透了校园内网,这里记录下自己搭建过程。 其实一开始我打算直接用开发比较成熟的花生壳软件做内网穿透,但是不知道怎么回事,显示连接成功但是ssh远程登陆不上,后来就放弃了,最后决定用自己的服务器和域名穿透(后来我还申请了花生壳学生版,羊毛先薅到以后再说用不用)。 frp是一个go语言写的开源内网穿透和反向代理软件,支持tcp, udp, http, https等协议,支持linux、mac、windows平台,操作也很方便,非常适合我这种小白。 1 下载frp源代码作者发布在github,点击这里。 选择最新的release版本,注意frp在service端和client端有两个不同的程序和配置文件,service端是你想要做反向代理的有公网ip的服务器,client端是处于内网之下的你想要穿透的服务器。 service端和client端一定要同一个版本。这里我的service端和client端都是linux操作系统,所以我直接下载了linux_arm64.tar.gz(我想顺便远程操控实验室电脑,所以也下载了windows版本,默认windows远程桌面端口号是3389,这个以后再说)。 将tar.gz文件传到两台服务器上,tar -zxvf解压就可以使用了(不需要编译,就是这么简单)。 在service端保留frps程序和相应的ini配置文件,在client端保留frpc程序和相应的ini配置文件(主要防止自己搞错)。配置文件有两种,我们可以选择其中一个;ini是最简单的配置文件,full.ini配置文件中记录了全部配置参数和英文解释,需要的时候可以自己根据情况修改。 2 修改配置文件网上的教程很多,full.ini也记载了全部的配置方法,我这里只记录下我自己的配置(敏感信息就不展示了)。 2.1 service端配置frps.ini配置文件修改如下: 123456789101112131415[common]bind_port = 7000 # frp监听的端口,默认7000,可改bind_udp_port = 7400 # UDP通讯端口,可不设置,用于点对点穿透token = xxxxxxxx # 安全考虑需要设置口令,client端需要用到dashboard_port = 7500 # frp管理端口,可改dashboard_user = xxxx # 管理端口认证的用户名,用于身份识别,自己设置dashboard_pwd = xxxx # 管理端口认证的密码,用于身份识别,自己设置enable_prometheus = truesubdomain_host = xxx.xxx.xxx # 设置子域名,主要方便登录管理界面。不用ip地址,用域名+端口的方式直接访问log_file = /usr/local/frp/frps.log # frp日志配置,这里是记录3天的日志信息log_level = infolog_max_days = 3 子域名设置主要是方便登录管理界面,不是必须的,反正我记不住服务器一长串ip地址…这个域名需要DNS解析后才能使用 后台不挂起运行frps: 1nohup ./frps -c frps.ini & 这个时候我们是看不到运行日志的,打开刚刚设置的frps.log文件 几个设置端口都监听成功,最后也显示frps started successfully说明开启成功。 2.2 client端配置frpc.ini配置文件修改如下: 12345678910[common]server_addr = xxx.xxx.xxx.xxx # 填写你的service端服务器公网ip,这里我写我的云服务器ipserver_port = 7000 # 前面设置的frp监听端口,需要保持一致token = xxxxxxxx # 前面设置的口令[ssh] # 这里只演示ssh端口的映射,其他参考frpc_full.initype = tcp # tcp协议local_ip = 127.0.0.1 # 这个地址代表本机local_port = 22 # ssh端口,默认22,由你ssh登录的client服务器决定remote_port = 6000 # 映射的service端服务器的端口,自己定义 注意下remote_port这个设置的是service端也就是云服务器的端口,通过这个端口访问client端的22端口,也就是端口映射。 同样的后台不挂起运行frpc: 1nohup ./frpc -c frpc.ini & 打开nohup的输出文件: 显示login to service success表示和service端连接成功。 全部设置完成后,理论上我就可以通过云服务器的主机地址+6000端口,通过ssh方式访问学校内网中的集群主机地址+22端口了。 但是我的云服务器比较特殊,还需要进行一步开放防火墙端口。 3 开放serviced服务器端口如果在2.2这一步配置之后一直连不上service端,那极有可能是service服务器的端口没有开放。 特别注意一点,如果是买的云服务器(比如我买的腾讯云服务器),不仅要在控制台页面开放端口,还需要在linux云服务器开放端口。举个例子,我们这里用到的云服务器端口是7000,7400,7500和6000,首先要在控制台防火墙页面 开放这几个端口。 然后在云服务器上打开防火墙,开放对应端口: 1234systemctl start firewalld # 打开防火墙firewall-cmd --permanent --add-port=7000/tcp # 永久开放指定的7000端口(其他端口同理)firewall-cmd --reload # 重启防火墙firewall-cmd --list-ports # 查看防火墙开放的所有端口 注意一下防火墙端口设定完成后,需要重载防火墙才会生效。 我们把自己云服务器的防火墙和端口配置好就行(学校集群你不是root用户你也配置不了,一般来说也不会设置防火墙)。 4 frp管理面板有两种方式可以访问: service端服务器 ip地址:端口号 设置子域名后可以用 子域名:端口号 用户名密码认证后,可以看到如下页面: 主要就是看一下连接数量,连接方式,产生的流量等等,具体就不细说了。 开个手机热点,用xshell登陆一下集群,发现显示的登录ip变成了localhost,而不是10开头的A类地址了,说明反向代理成功。 连接速度非常快,而且稳定!以后登录集群就再也不用校园网啦! 5 写在最后这种用外网服务器做反向代理服务器,通过端口转发的方式访问内网服务器还是有一定安全风险的,该开防火墙开防火墙,小心驶得万年船。 还有,这种方法也有个缺点。打个比方如果你在校外,而学校服务器因为某种不可抗力重启了(比如停电,这在新疆真的太常见了)这就相当于你挂在后台nohup的程序被强制杀掉了。等到管理员重启后,client端的frpc程序就需要再执行一次才能生效,这个时候就只能拜托有学校集群账号的人帮你在后台执行nohup,你才能从外网访问集群。 要应对这种情况,最简单的是你写个开机自启动脚本执行frpc,但是你没有root权限是不可行的…或者你让集群管理员给你su权限,这一般来说也不太可能…如何完美解决这个问题还有待研究","categories":[{"name":"网络相关","slug":"网络相关","permalink":"http://www.shelven.com/categories/%E7%BD%91%E7%BB%9C%E7%9B%B8%E5%85%B3/"}],"tags":[{"name":"内网穿透","slug":"内网穿透","permalink":"http://www.shelven.com/tags/%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F/"},{"name":"反向代理","slug":"反向代理","permalink":"http://www.shelven.com/tags/%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86/"}]},{"title":"blastn & blastp 寻找同源基因","slug":"blastn-blastp-寻找同源基因","date":"2022-07-05T11:25:26.000Z","updated":"2022-12-03T16:11:26.000Z","comments":true,"path":"2022/07/05/a.html","link":"","permalink":"http://www.shelven.com/2022/07/05/a.html","excerpt":"今天接到一个任务,大致内容是在一个植物的全长转录组数据中找拟南芥的三个同源基因。简简单单的描述,我的想法也很简单,直接找基因的CDS序列做blastn比对就完事了,结果却没有那么顺利…记录一下踩的坑和解决办法。","text":"今天接到一个任务,大致内容是在一个植物的全长转录组数据中找拟南芥的三个同源基因。简简单单的描述,我的想法也很简单,直接找基因的CDS序列做blastn比对就完事了,结果却没有那么顺利…记录一下踩的坑和解决办法。 1 blastn寻找同源基因三个基因TAIR号是AT4G28590、AT2G43010和AT2G34640,从全长转录组测序报告中,我找到了非冗余的转录本序列文件CD-hit-est.fasta,首先第一步就是本地建核酸序列库。 1makeblastdb -in CD-hit-est.fasta -dbtype nucl -input_type fasta -out Kc 因为给的是TAIR号,所以直接去TRIR官网查找相应基因的CDS序列做比对 手动创建query gene的fa序列文件 1234vim RCB_cds.fna# 手动创建fa文件>AT4G28590ATGAGTTTCTTCGCTGTTGCTTGCTCCGCGCCAAGATCTTCTATGCTTCTCACCGGCTTGAATTCGAGCTTCTCTGATATGCATCGCAGCCCACTATTTGTTTTCCCGGTGACTATATCATCCCGGAGCGTGAAACGCTTCGCCGCTGTTTCGTCTGATTCCGTACTAGACCCTGAATCCAAAAATCAAACTCGGTCCCGTCGCAAAAATAAGGAAGCAGTTACGCCAATTGCTGAAACCGAGAACAATGAAAAGTTTCCGACAAAGGTCCCGCGTAAATCGAAGCGTGGGCGGCGGAGTGAAGCAGACGCTGTGGAAGATTACGTGAGAAGCTCCCTCGAGCGTACTTTCTCCACCATAAAGGAGCAGAATCCGGAGGTTTTTGAGAACAAGGAGAAGGCGAATTTCATCAAAGACAGAGGCGTTGATGAAGAAGAGGAAGAAGAAGAAGAGATGGTGGTGGAAGAGGAAGATCCAGATTGGCCAGTAGATACAGACGTTGGATGGGGAATCAAAGCTTCGGAGTATTTCGATACACATCCAATCAAAAACGTGGTTGGAGATGATGGGAGTGAGATTGATTGGGAAGGTGAGATTGATGATAGTTGGGTCAAGGAGATCAATTGTTTGGAATGGGAAAGCTTTGCTTTTCATCCTAGTCCACTCGTTGTCCTTGTATTCGAGCGATACAAAAGAGCTAGTGATAACTGGAAGACATTGAAGGAGCTTGAGAAAGCTATCAAAGTTTATTGGGATGCGAAAGATCGATTACCTCCACGGGCGGTTAAGATTGACCTGAACATCGAGACAGATTTGGCATATGCTCTTAAAGCTAAGGAATGCCCACAGATTCTCTTCTTACGCGGAAACCGGATTCTGTACAGGGAGAAAGACTTTCGCACGGCGGATGAATTGGTTCATATGATTGCGCATTTCTACTATAAAGCGAAGAGGCCTTCGTGTGTCGACAAGGCTAATGTAACCCCGTACTGTTAG blastn比对 1blastn -query RCB_cds.fna -out RCB_blastn_Kc.out -db Kc -outfmt 6 -evalue 1e-5 -num_threads 4 RCB_blastn_Kc.out是blast的m8格式输出文件,找到匹配长度最长的(也就是第一条)subject gene id,回到非冗余转录本,找到subject gene在哪行,最后找出转录本序列。 1234# 找到subject gene所在行(subject gene id中有所在行数,这里验证下)cat CD-hit-est.fasta | grep -n "Kc-zong_1-10k_transcript/10791"# 提取序列awk 'NR>=10719 && NR<=10720' CD-hit-est.fasta 紧接着出现一个问题:AT2G43010和AT2G34640这两个基因无法通过blastn比对找到同源序列,evalue值不管放到多宽都比对不上。 因为这个植物在NCBI上没有参考基因组,我们课题组也只测了全长转录组而没有测基因组,所以当一开始没有比对出结果的时候,我一度怀疑是这种植物压根儿就没有这俩基因,或者这个样品叶片(测序的部位)在检测的时间点就没有转录相应的基因。 本地blast找不到同源基因,我又从近缘菊科植物开始折腾,思路是如果菊科有同源基因则寻找保守结构域,设计引物将CDS区域克隆出来。至今已发表的植物基因组可以从网站Plabipd(本站网址导航栏有收录)找到,这个网站很贴心地把物种种属关系也列了出来,可以很方便地找物种学名和近缘关系。 理想很丰满现实很骨感,我从菊科一级一级往上找,直到Eudicotyledoneae(真双子叶植物分支)才用blastn比对上同源基因,而且无一例外比对上的全是十字花科(拟南芥所在科)植物,根本不算近缘物种….无奈之下试了blast的其他功能,用氨基酸序列跑了一遍blastp,然后发现菊科也有序列可以比对上了!这才打开新世界的大门 2 blastp寻找同源基因基于翻译阅读框对去冗余的全长转录本进行CDS预测(TransDecoder软件),结果以fasta格式保存,后续我会对这个文件验证一遍,先建蛋白库做blastp比对。 1makeblastdb -in transdecoder.pep.fa -dbtype prot -input_type fasta -out nr_Kc 可以看到只有25128个编码蛋白基因,对于基因组大小在1G左右的菊科物种来说,这个基因数量过少。因此后续还需要对全长转录组数据再跑一遍验证一下,这个是后话。 通过TAIR号在TAIR官网查找蛋白序列,创建fa文件后进行本地blastp比对 12blastp -query PIF4_pep.fna -out PIF4_blastp_nr_Kc.out -db nr_Kc -outfmt 6 -evalue 1e-5 -num_threads 4blastp -query HMR_pep.fna -out HMR_blastp_nr_Kc.out -db nr_Kc -outfmt 6 -evalue 1e-5 -num_threads 4 注意下结果文件名写清楚什么基因,用的什么方法比对,比对的什么库。这个时候再查看各自的结果文件,发现有比对结果,再回到非冗余转录本文件找对应的cds序列。操作过程都一样,这里不再赘述了。 3 总结找三条同源基因花了一整天的时间,主要原因还是对同源序列了解不够深刻。 同源就是有共同的进化祖先,序列相似性搜索可以通过检测过高的相似性来识别同源蛋白质或基因:当两个序列的相似性超过偶然的预期时,我们推断这两个序列存在同源性。 当观察到过高的相似性时,这两个序列不是独立出现的,它们起源于一个共同的祖先。 通过算法进行序列对库比对的工具,比如blast等,是通过过高相似性来减少假阳性的结果。所以通过算法在统计学上找不到库里显著的匹配项,不代表这个物种中一定没有同源基因。 从这次blastn和blastp比对结果来看,核酸序列比对可能更不容易找到同源序列。其实也好理解,生物在进化的几亿年时间里,很难保证不同物种有高相似性的核酸序列。同个氨基酸有不同密码子(简并性),也能证明蛋白质一级结构才是对生物影响最大的,蛋白质序列相同,就会有相似结构和功能。因此,蛋白质序列也就是氨基酸序列,对相似性的搜索比核酸序列要敏感的多。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"blast","slug":"blast","permalink":"http://www.shelven.com/tags/blast/"}]},{"title":"0基础学习基因组三代测序组装(4)——初步组装二代数据","slug":"0基础学习基因组三代测序组装(4)——初步组装二代数据","date":"2022-07-03T11:28:52.000Z","updated":"2022-12-03T16:12:27.000Z","comments":true,"path":"2022/07/03/a.html","link":"","permalink":"http://www.shelven.com/2022/07/03/a.html","excerpt":"经过前面的全基因组特征调查(survey)后,我们发现这是一个复杂基因组,杂合度较高,可以以二代+三代测序技术相结合的策略进行全基因组组装,还可以以Hi-C(高通量染色体捕获技术,High-through chromosome conformation capture)技术进行辅助组装。这里我用华大开发的二代测序组装工具SOAPdenovo,用二代测序数据对进行初步基因组组装。","text":"经过前面的全基因组特征调查(survey)后,我们发现这是一个复杂基因组,杂合度较高,可以以二代+三代测序技术相结合的策略进行全基因组组装,还可以以Hi-C(高通量染色体捕获技术,High-through chromosome conformation capture)技术进行辅助组装。这里我用华大开发的二代测序组装工具SOAPdenovo,用二代测序数据对进行初步基因组组装。 1 安装SOAPdenovo 2.0github上这个软件的版本是2.0,网址点击这里 软件下载安装过程非常顺利,如果有报错无法解决的话可以在Issue里向作者反馈。 1234# 集群如果无法登录github,下载源码包,通过xftp传到集群tar -zxvf SOAPdenovo2cd SOAPdenovo2-r242make 编译之后可以看到有如下几个文件 SOAPdenovo-127mer和SOAPdenovo-63mer是用于组装的两个程序,分别代表支持的最大k-mer为127和63,用法上是完全相同的。 example.config是配置文件,组装之前我们要设置其中的参数内容;README.md是帮助文件,详细记录了各项参数的作用和设置方法。这个后面会讲到。 2 kmergenie计算最佳k值现在组装基因组的算法主要有三种:De Bruijn graph,Overlap-Layout-Consensus和String Graph。SOAPdenovo软件组装基因组用的是De Bruijn graph算法,简单理解是通过将reads打断成k-mer后,利用k-mer之间的重复部分构建图,得到最优化路径从而拼接contig。要具体了解什么是De Bruijn graph,可以参考这一篇博文。 不同k-mer值构建的De Bruijn graph不一样,会导致组装质量的差异,因此我们需要选择一个最佳的组装k-mer大小(尽管可以用默认值23直接组装,但是效果不一定是最好的)。 kmergenie软件和之前的Jellyfish类似,都可以用于统计k-mer数量,kmergenie最大优点是可以对预设的多个k-mer进行分析,找到最佳的k-mer。点击这里进入Kmergenie官网,下载最新版本的软件。 注意下这个软件安装需要python > 2.7,并且需要安装R和zlib。 12345678tar -zxvf kmergenie-1.7051.tar.gzcd kmergenie-1.7051makepython setup.py install --user # 安装到用户环境中,不报错说明可以使用vim file.txt # 将两个fq文件路径写进去,一行一个/public/home/wlxie/biosoft/kmergenie-1.7051/kmergenie file.txt -o ./kmergenie_res -l 15 -k 65 -s 5 -t 30 --diploid # 运行kmergenie -o # 输出文件位置和名称 -l # 设定的最小k值 -k # 设定的最大k值 -s # 最小k值到最大k值,每次增加的间隔(根据需要设定间隔大小) -t # 运行的线程(CPU核)数 --diploid # 二倍体模式,前面我们已经用jellyfish确认过这是个复杂基因组。默认是单倍体模式 其原理就是设置不同k值进行基因组大小预估,将组装的基因组最大的k值作为最佳k值。 最终会给出kmergenie_res为前缀的一系列报告,生成的.histo文件还可以用来上一篇笔记中的GenomeScope分析,这里我们只需要看总结的html文件。 确定组装的最佳k值为51 3 SOAPdenovo2组装contigs/scaffolds复制一份example.config配置文件,重命名为run_config,修改部分参数 123456789101112131415161718192021222324252627282930#maximal read length#全局配置参数,只要高于这个参数的序列都会被截取到这个长度max_rd_len=150#文库配置以[LIB]开头[LIB]#average insert size#文库插入片段的平均长度,在实际设置时,可以参考文库size分布图,取峰值(默认200)avg_ins=200#if sequence needs to be reversed#是否需要将序列反向互补,对于pair-end数据,不需要反向互补,设置为0;对于mate-pair数据,需要反向互补,设置为1reverse_seq=0#in which part(s) the reads are used#1表示只组装contig,2表示只组装scaffold,3表示同时组装contig和scaffold,4表示只补gapasm_flags=3#use only first 100 bps of each read#序列长度阈值,作用和max_rd_len相同,大于该长度的序列会被切除到该长度rd_len_cutoff=150#in which order the reads are used while scaffolding#设置不同文库数据的优先级顺序,取值范围为整数,rank值相同的多个文库,在组装scaffold时,会同时使用。rank=1#cutoff of pair number for a reliable connection (at least 3 for short insert size)#contig或者scaffold之前的最小overlap个数,对于pair-end数据,默认值为3;对于mate-paird数据,默认值为5pair_num_cutoff=3#minimum aligned length to contigs for a reliable read location (at least 32 for short insert size)#比对长度的最小阈值,对于pair-end数据,默认值为32;对于mate-pair数据,默认值为35map_len=32#a pair of fastq file, read 1 file should always be followed by read 2 file#过滤后的双端测序数据文件路径,q为fastq格式,f为fasta格式,b为bam格式q1=/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Second-generation_sequencing/20211106-BaiYiHuiNeng01/01.rawFq/00.mergeRawFq/1/clean_data/1_r aw_1_val_1.fqq2=/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Second-generation_sequencing/20211106-BaiYiHuiNeng01/01.rawFq/00.mergeRawFq/1/clean_data/1_r aw_2_val_2.fq SOAPdenovo有6个子命令pregraph、sparse_pregraph、contig、map、scaff和all,前5个命令对应5个组装步骤,第一和第二是两种不同构图方式,all命令一次执行所有步骤,用all命令比较省事儿。 SOAPdenovo命令还有一些参数用于调整,参数参考 123456789101112131415-s # 配置文件-o # 输出文件的前缀-K # 输入的K-mer值大小,默认值23-p # 程序运行时设定的线程数,默认值8-R # 利用read鉴别短的重复序列,默认值不进行此操作-d # 去除频数不大于该值的k-mer,默认值为0-D # 去除频数不大于该值的由k-mer连接的边,默认值为1,即该边上每个点的频数都小于等于1时才去除-M # 连接contig时合并相似序列的等级,默认值为1,最大值3。-F # 利用read对scaffold中的gap进行填补,默认不执行-u # 构建scaffold前不屏蔽高覆盖度的contig,这里高频率覆盖度指平均contig覆盖深度的2倍。默认屏蔽-G # 估计gap的大小和实际补gap的大小的差异,默认值为50bp。-L # 用于构建scaffold的contig的最短长度,默认为:Kmer参数值 ×2-k # map步骤中kmer的大小,默认是和K一样的kmer大小-N # 基因组大小-V # 输出可视化的组装信息 运行组装命令 1/public/home/wlxie/biosoft/SOAPdenovo2-r242/SOAPdenovo-63mer all -s /public/home/wlxie/biosoft/SOAPdenovo2-r242/run_config -K 51 -R -V -o A_venetum -p 30 程序运行了3个小时,结束后生成了以下文件 4 组装结果解读组装结果文件其实只有两个,分别以**.contig结尾和.scafseq结尾**。因为我是在集群上运行的,slurm-11168.out是集群的输出日志文件,记录了详细的组装过程和结果。 最终得到935861个contigs,总长度295302126 bp,平均长度315 bp,最长的长度38673 bp,contig N50是532 bp,contig N90是103 bp;scaffold个数77918,总长度192992858 bp,平均长度2476 bp,最长的长度108587 bp,scaffold N50是3385 bp,scaffold N90是130 bp。(可以做一个统计表) 从组装的contig覆盖深度和数量还可以做一个柱状图,理论上来说是和前面k-mer分布图呈现一样的趋势,也就是一个主峰和一个杂峰,两个图相互印证目标基因组是个复杂基因组。 其他的结果文件在github上有解释,我先直接复制过来,以后用到再翻译翻译…… 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950511. Output files from the command "pregraph"a. *.kmerFreq Each row shows the number of Kmers with a frequency equals the row number. Note that those peaks of frequencies which are the integral multiple of 63 are due to the data structure.b. *.edge Each record gives the information of an edge in the pre-graph: length, Kmers on both ends, average kmer coverage, whether it's reverse-complementarily identical and the sequence.c. *.markOnEdge & *.path These two files are for using reads to solve small repeats.e. *.preArc Connections between edges which are established by the read paths.f. *.vertex Kmers at the ends of edges.g. *.preGraphBasic Some basic information about the pre-graph: number of vertex, K value, number of edges, maximum read length etc. 2. Output files from the command "contig"a. *.contig Contig information: corresponding edge index, length, kmer coverage, whether it's tip and the sequence. Either a contig or its reverse complementry counterpart is included. Each reverse complementary contig index is indicated in the *.ContigIndex file.b. *.Arc Arcs coming out of each edge and their corresponding coverage by readsc. *.updated.edge Some information for each edge in graph: length, Kmers at both ends, index difference between the reverse-complementary edge and this one.d. *.ContigIndex Each record gives information about each contig in the *.contig: it's edge index, length, the index difference between its reverse-complementary counterpart and itself. 3. Output files from the command "map"a. *.peGrads Information for each clone library: insert-size, read index upper bound, rank and pair number cutoff for a reliable link. This file can be revised manually for scaffolding tuning.b. *.readOnContig Reads' locations on contigs. Here contigs are referred by their edge index. Howerver about half of them are not listed in the *.contig file for their reverse-complementary counterparts are included already.c. *.readInGap This file includes reads that could be located in gaps between contigs. This information will be used to close gaps in scaffolds if "-F" is set. 4. Output files from the command "scaff"a. *.newContigIndex Contigs are sorted according their length before scaffolding. Their new index are listed in this file. This is useful if one wants to corresponds contigs in *.contig with those in *.links.b. *.links Links between contigs which are established by read pairs. New index are used.c. *.scaf_gap Contigs in gaps found by contig graph outputted by the contiging procedure. Here new index are used.d. *.scaf Contigs for each scaffold: contig index (concordant to index in *.contig), approximate start position on scaffold, orientation, contig length, and its links to others contigs.e. *.gapSeq Gap sequences between contigs.f. *.scafSeq Sequences of each scaffolds.g. *.contigPosInscaff Contigs' positions in each scaffold.h. *.bubbleInScaff Contigs that form bubble structures in scaffolds. Every two contigs form a bubble and the contig with higher coverage will be kept in scaffold.i. *.scafStatistics Statistic information of final scaffold and contig.","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"kmergenie","slug":"kmergenie","permalink":"http://www.shelven.com/tags/kmergenie/"},{"name":"SOAPdenovo2","slug":"SOAPdenovo2","permalink":"http://www.shelven.com/tags/SOAPdenovo2/"}]},{"title":"0基础学习基因组三代测序组装(3)——全基因组Survey","slug":"0基础学习基因组三代测序组装(3)——全基因组Survey","date":"2022-07-02T07:08:17.000Z","updated":"2022-12-03T16:13:20.000Z","comments":true,"path":"2022/07/02/a.html","link":"","permalink":"http://www.shelven.com/2022/07/02/a.html","excerpt":"之前说到如何对三代测序数据做污染评估,取随机序列做blastn比对nt库,确定物种分布情况。实际blast比对还要考虑比对的序列长度和ONT本身数据错误率,以及结合GC-depth确定是否有污染。基因组三代测序数据组装之前,我们还要做一个全基因组survey。主要是为了减少盲目性,先做低深度的基因组分析,也是初步了解物种基因组特征的有效方法,比如评估基因组大小和杂合情况,为后续全基因组de novo组装策略指定提供指导。","text":"之前说到如何对三代测序数据做污染评估,取随机序列做blastn比对nt库,确定物种分布情况。实际blast比对还要考虑比对的序列长度和ONT本身数据错误率,以及结合GC-depth确定是否有污染。基因组三代测序数据组装之前,我们还要做一个全基因组survey。主要是为了减少盲目性,先做低深度的基因组分析,也是初步了解物种基因组特征的有效方法,比如评估基因组大小和杂合情况,为后续全基因组de novo组装策略指定提供指导。 基因组复杂程度的经验性标准: 简单基因组: 单倍体;或纯合二倍体;或杂合度低于0.5%, 且重复序列低于50%, 且GC含量在35%-65%的二倍体。 复杂基因组: 杂合度在0.5%~1.2%之间,或重复序列高于50%,或GC含量异常(<35%或>65%)的二倍体,或者多倍体。复杂基因组可以采用“2+3”即二代和三代测序技术相结合,加之Hi-C辅助组装的组装策略。 高复杂基因组: 杂合度>1.2%;或重复序列占比大于65%。 有条件的话,也可以用流式细胞仪对基因组大小做个预估。我这里只有二代基因组测序数据,因此用基因组二代测序数据做全基因组survey。当然,这里要注意一点,做全基因组survey的样本和后续de novo组装的样本要来自同一个个体,避免个体间基因组特征的差异。 1 原始数据质控因为是对二代测序数据进行分析,质控的过程本质上和之前处理转录组二代数据一样,这里只提下过程和结果。 1.1 fastqc生成质控报告1fastqc *.fq.gz -o ./ 二代测序是双端测序结果,我这里只截图了部分qc报告,可以看出GC含量比较稳定,测序质量也比较高。 1.2 trim-galore数据过滤报告中的结果虽然好,但是还是需要过滤一遍,把末端接头adapter序列过滤掉。 1trim_galore -q 25 -phred33 -length 100 -stringency 1 -paired -o clean_data 1_raw_1.fq.gz 1_raw_2.fq.gz 参数在这篇博客 转录组数据分析笔记(1)——如何用fastqc和trim-galore做测序数据质控 有提到,这里不再赘述。 看下report文件,过滤了Q值25以下的reads和adapter序列 2 k-mer分析先说一下k-mer的概念:k-mer在这里指将reads迭代拆分成包含k个碱基的序列(类似blast中的word length,蛋白质是3,核酸是11),我们后面要分析的基因组特征都是基于k-mer分布基础上进行的。 基因组大小可以通过总 (K-mer 数量)/(K-mer 期望测序深度)来估计 k-mer分布曲线的主峰所在横坐标可以作为期望的测序深度 测序覆盖均匀、不存在测序误差和基因组重复序列的理论条件下,K-mer分布曲线会符合泊松分布 单倍体或纯合基因组的 K-mer 分布曲线只有一个主峰 杂合二倍体基因组的 K-mer 分布曲线有两个峰, 分别为杂合峰(主峰1/2处)和纯合峰(主峰),前者深度只有后者的一半 重复序列含量较高时会在主峰后面形成一个重复峰(主峰的2倍处)或者形成拖尾 一般选择17-mer评估基因组大小,因为ATCG组成长度为17的核酸序列,理论上有4的17次方种可能,足以覆盖一般的正常基因组。为了避免回文序列,K-mer分析选择K长度均为奇数。 2.1 安装jellyfish根据上面说的k-mer概念,可以理解k-mer分析是非常耗计算资源的。我们要自己用脚本实现的话,需要将十几个G的reads分割成不同长度片段,再统计出现的次数,耗时而且麻烦。jellyfish是一款统计DNA序列中Kmer的分布的软件,它运行速度快,内存消耗低,支持并行,也是用的最多的统计k-mer的软件。 重点是可以通过conda直接安装……最好不要用conda安装,我之前运行了1天没出结果也没报错(一度怀疑我的参数设置是不是有问题),百思不得其解。后来从github上重新下载,编译和安装之后,不到10分钟就跑出结果了…我不知道两种安装方式有什么区别,这里就记录下自己踩的坑。 因为jellyfish不支持.gz的压缩文件,所以之前用tram galore过滤后得到的clean reads需要用gunzip命令解压。 1conda install -c bioconda jellyfish # 可以用conda安装,我运行的时候出了问题,暂未解决,不推荐 点击这里进入jellyfish的github下载地址 我们用本地安装的方式,先下载tar.gz的源码包,tar -zxvf解压后进入jellyfish-2.3.0文件夹。 我是集群登录的,下面讲的步骤都是在集群上操作(非root账户) 12345678# 第一步检测。本质上是一个shell脚本,根据系统环境产生合适的makefile文件或者C的头文件(.h结尾的文件),非root账户下--prefix后面接上自己账户的绝对路径。./configure --prefix=/public/home/wlxie# 第二步编译。对源代码包进行编译,如果有错误自己看是否有依赖库的缺失,主要是这个问题。make# 第三步安装。如果前面没有指定自己账户的路径,这一步是会报错没有权限的(用户不能向系统目录写入文件)。make install# 第四步自检。make check make和make check这两步都会因为动态链接库命名不同,导致报错无法找到动态库;以及我在检测通过之后,用集群运行程序仍然出现了动态库的某个模块无法调用的情况。这里统一说下解决方法。 前面configure会在我们的家目录下生成bin、lib和share目录,这里比较重要的是bin和lib目录。我们运行的命令在bin目录里,对应要改环境变量PATH;而需要调用的动态库是在lib目录下,对应要改环境变量LD_LIBRARY_PATH。家目录下的.bashrc文件加入以下内容 12export PATH="/public/home/wlxie/bin:$PATH"export LD_LIBRARY_PATH="/public/home/wlxie/lib:$LD_LIBRARY_PATH" 添加之后保存退出,并且source ~/.bashrc刷新一下系统环境变量。 我碰到的报错是libcrypto.so.1.0.0和libstdc++.so.6这两个动态库找不到,但是locate命令查看这两个动态库,在系统目录/lib64/下都能找到文件,因此将这两个动态库文件直接复制到家目录的lib文件夹,问题就全部解决了。 如果libstdc++.so.6报错某版本的文件不存在,可以先到动态库目录下,运行strings命令查看动态库中是否有对应的文件: 1strings /lib64/libstdc++.so.6 | grep CXXABI # 比如找不到GLIBCXX_3.4.26,看看动态库中是否存在这个版本的文件,如果不存在,更新动态库;如果存在但是找不到,建议直接拷贝到自己的lib目录下 make check之后会生成一个日志文件test-suite.log,没有fail的项目说明软件安装成功,没有问题。 2.2 k-mer频数分布123456# k-mer计数jellyfish count -m 17 -s 300M -t 50 -C -o 17-mer.jf ./1_raw_1_val_1.fq ./1_raw_2_val_2.fq# k-mer频数统计jellyfish histo -t 4 17-mer.jf > 17-mer.histo# 统计总k-mer数和特征k-mer数等jellyfish stats 17-mer.jf -o counts_stats.txt 记录一下各个参数的意义: -m # k-mer长度设置为17bp,进行计数 -s # 存储用的hash表大小,说实话我没看懂什么意思,基因组估计有多大就用多大就是了,单位是M或者G -t # 使用的线程数,也就是cpu核数 -C # 大写的C,对正负链reads都进行统计,双端测序一定要加这个参数 -o # 结果文件的前缀名,结果文件是一个二进制文件 正常来说,10分钟就能跑完程序并给出k-mer计数结果文件。我用conda安装的jellyfish同样条件运行了20个小时没有结束……而且还不报错!第一次运行这个软件,没有人参考和交流,百度到的教程都是抄来抄去的也没有人说明时间的问题……以后还是去官网安装生信软件了,虽然麻烦一点但是靠谱…… 这个软件的帮助文档在/jellyfish-2.3.0/doc目录下,所有功能和参数都有英文详解。 k-mer频数统计是在计数结果文件上进一步统计各个k-mer出现的次数,频数统计结果文件17-mer.histo将k-mer从1统计到10000,最后一行是10001以后对应的总频次。counts_stats.txt是总的统计结果,包括k-mer总数(Total),特异的k-mer数目(Distinct)只出现过一次的k-mer数量(Unique),频数最高的k-mer数量(Max_count)四项。 有了频数统计结果文件17-mer.histo就可以用R作图了,以下R作图代码来自于CSDN博主 生信技工 1234567kmer <- read.table('17-mer.histo')kmer <- subset(kmer, V1 >=5 & V1 <=500) # 只取5-500bp长度的k-mer统计频次Frequency <- kmer$V1Number <- kmer$V2png('kmer_plot.png')plot(Frequency, Number, type = 'l', col = 'blue')dev.off() # 保存png k-mer分布图如下,当然这只是一个简略图,上面R作图代码还有很多细节可以补充 2.3 基因组大小、重复率、杂合率估算横坐标表示k-mer深度,纵坐标为k-mer数量,可以看得出来测序的样本是个杂合二倍体。主峰坐标(116,2584902),杂合峰坐标(57,1188461),也就是说k-mer期望深度为116;k-mer总数为34655456060;主峰2倍深度也就是232之后的k-mer为重复序列k-mer,总数可以通过导出17-mer.histo文件进行统计(改成csv格式直接两步算出),共16361378388 k-mer分布曲线中无异常峰,说明二代测序提取的DNA纯度较高,没有被污染 根据(K-mer 数量)/(K-mer 期望测序深度)估算基因组大小为298M。去除深度小于5的错误k-mer,估算基因组大小为292M. 根据(重复序列的k-mer总数)/(K-mer 期望测序深度)估计重复序列大小为141M,即重复率48.29% 单拷贝序列大小U=292-141=151M,要计算杂合率,需要统计非重复k-mer的总数,也就是计算杂合峰面积,建议还是用软件或者在线工具比如genomescope2.0 jellyfish + GenomeScope是一套应用非常广泛的基因组survey方法,GenomeScope2.0适合用于分析二倍体生物。 上面是GenomeScope2.0网页版界面,只要我们提供jellyfish生成的.histo结果文件,设置参数就行 k-mer length # k-mer长度 Ploidy # 染色体倍性 Max k-mer coverage # 默认-1,即不限制最大k-mer深度,我这里限制了10000 Average k-mer coverage for polyploid genome # 默认-1,不进行筛选 提交后几分钟就生成了可用于发表的图和报告 可以看到估计的基因组大小是200M,杂合率0.865%,杂合峰覆盖度(深度)58.3。下面说下这个图如何解读: 蓝色区域是实际观测值 黑色拟合线是去除错误(errors)后剩下的k-mer分布,认为是正确的数据并以此评估基因组大小 黄色拟合线是非重复区域的k-mer分布(理想情况) 橙色拟合线区域是低深度的错误k-mer,认为是测序错误引入的 黑色虚线是k-mer的几个峰值 之所以估计的基因组大小比之前自己估计的要小,是因为去除error的标准不同,我之前只是简单去除了k-mer深度1-4的错误序列,这里是构建模型选择的错误序列,更准确一些。 网页版最后的results里还有总的统计结果,可以很方便地计算重复率,一眼就能看明白这里就不赘述了。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"jellyfish","slug":"jellyfish","permalink":"http://www.shelven.com/tags/jellyfish/"},{"name":"GenomeScope2.0","slug":"GenomeScope2-0","permalink":"http://www.shelven.com/tags/GenomeScope2-0/"}]},{"title":"0基础学习基因组三代测序组装(2)——数据污染评估","slug":"0基础学习基因组三代测序组装(2)——数据污染评估","date":"2022-06-19T17:17:30.000Z","updated":"2023-01-13T11:02:00.000Z","comments":true,"path":"2022/06/20/a.html","link":"","permalink":"http://www.shelven.com/2022/06/20/a.html","excerpt":"做完基因组三代测序数据质控之后,我们把所有reads的Q值控制在7以上,每个read的长度在1000bp以上。我们不能明确自己的测序数据是否被其他物种污染,这个时候就要用balst比对的方法确定测序数据是否被污染,以及污染的来源。","text":"做完基因组三代测序数据质控之后,我们把所有reads的Q值控制在7以上,每个read的长度在1000bp以上。我们不能明确自己的测序数据是否被其他物种污染,这个时候就要用balst比对的方法确定测序数据是否被污染,以及污染的来源。 1 下载balst+工具和数据库在之前的一篇博客中,我详细介绍了如何本地安装NCBI的blast+工具,以及下载nr/nt库,建立本地的数据库。详情点击这里。 在做数据污染评估的时候,我们还需要知道blast最佳结果对应的物种名,因此还需要下载分类数据库的以下两个子库: 12345678# ascp工具下载大数据,wget命令下载小文件(md5校验文件)ascp -QT -i /public/home/wlxie/miniconda3/envs/biosoft/etc/asperaweb_id_dsa.openssh -k1 -l 500m anonftp@ftp.ncbi.nlm.nih.gov:/pub/taxonomy/accession2taxid/nucl_gb.accession2taxid.gz ./wget https://ftp.ncbi.nlm.nih.gov/pub/taxonomy/accession2taxid/nucl_gb.accession2taxid.gz.md5ascp -QT -i /public/home/wlxie/miniconda3/envs/biosoft/etc/asperaweb_id_dsa.openssh -k1 -l 500m anonftp@ftp.ncbi.nlm.nih.gov:/pub/taxonomy/taxdump.tar.gz ./wget https://ftp.ncbi.nlm.nih.gov/pub/taxonomy/taxdump.tar.gz.md5 md5文件校验完成之后,两个数据库分别解压。注意.gz文件用gunzip,.tar.gz文件用tar -zxvf 看看这两个数据库长什么样: 第一张图片是nucl_gb.accession2taxid,我们需要用到第二列版本信息和第三列的taxid。 第二张图片是names.dmp,我们需要用到有taxid,学名和scientific name字符串的行。 这两个数据库怎么使用后面会详细说明。分析思路来自于CSDN的博主风风是超人,遗憾的是从17年开始,NCBI不再提供gi号与blastn结果的关联,博主的本地数据库可能版本比较早,采用的是gi号分析。 我将后续的代码做了修改,下载的也都是最新的数据库。总的逻辑是利用blast结果的version号,得到nucl_gb.accession2taxid数据库中的taxid号,最后通过names.dmp中的taxid号得到学名。代码方面做了少许优化,对集群服务器可能更友好一点? 2 fq文件处理和blast质控后的数据fq文件是“@”开头的,我们要改成fa格式也就是“>”开头。取前10000条序列,每个序列有4行,只取第一行标题和第二行序列。 12345# NR表示当前行,判断除以4的余数,余数1为标题行,只输出第一个元素即reads id;余数2则为序列行,输出所有元素也就是整条序列。最后替换@符号,文件名为test.fazcat clean_filter.fq.gz | head -n 40000 | awk '{if(NR%4==1){print $1}else if(NR%4==2){print $0}}' | sed 's/@/>/g' >test.fa# 批量blast程序blastn -query test.fa -out test_blastn_nt.xml -db nt -outfmt 5 -evalue 1e-5 -num_threads 20 -max_target_seqs 1 批量blast程序注意下我们输出的格式为xml格式,也就是- outfmt 5。为什么要用xml格式,因为xml格式能给出的信息最全,我们需要知道输出的版本号 evalue值根据需要设定,这里我设置1e-5 最大匹配数量注意下设置1,我们只需要知道和哪个物种相似度最高,一个输出结果就足够了(虽然设置1会有警告)。 看下blast生成的test_blastn_nt.xml这个结果文件: 虽然是第一次接触xml格式,但是感觉非常熟悉!之前做的一个微博爬虫小程序就是扒了一个类似的html格式的文件……xml格式也挺容易解析的,可以看到比对信息以标签<Iteration>开始,以</Iteration>标签结束,<Hit>标签开始表示的是比对上的结果(因为我设置了最大比对序列数量是1,所以<Hit_num>只有1);<Hsp>标签表示某一块的比对结果(同一条序列,若干片段比对上),因此<Hsp_num>标签的数量可能不止一个。 当然,这些都可以不用关心,分析需要的信息我用红框标了出来。比较重要的是<Hit_def>标签,里面的字符串是空格隔开的,第一个元素是我们需要的物种版本号。 3 XML文件解析前面说了解析的思路,以下是代码的实现。因为用的python语言写的程序,我的建议是在vscode一类的编程软件中写这些代码,如果有错误可以及时调试。 123456789101112131415161718192021222324252627282930313233import sysimport refrom collections import defaultdict# 可以不写,我是为了确保导入父目录的模块不出错sys.path.append("./")xmlfile = open("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/test_blastn_nt.xml","r")outfile = open("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/filted_accession_version.txt","w")# 定义一个空的字典,提取有queryID和subjectID的行dict1 = defaultdict(list)for lines in xmlfile: line = lines.strip() read_id = re.match('<Iteration_query-def>.*</Iteration_query-def>',line) Hit_def = re.match('<Hit_def>.*</Hit_def>',line) # 解析queryID if read_id != None: read_id = read_id.group() read_id = read_id.split("<")[1].split(">")[1] key=read_id # 字典key值和value值赋值 elif Hit_def !=None: Hit_def = Hit_def.group() Hit_def = Hit_def.split("<")[1].split(">")[1] dict1[key].append(Hit_def)# 写入文件,制表符分割for key in dict1: outfile.write(key + "\\t" + "\\t".join(dict1[key])+"\\n") 看下运行结束后解析得到的文件: 一共是两列,第一列是reads的queryID,第二列subjectID就是比对上的序列信息。可以看到第二列可以以空格为分隔符,提取第一个元素也就是物种版本号,后面会说。 为什么要把物种版本号提出来而不直接用这段内容里的物种名呢?因为不同的物种名字段数量和位置不一样,无法用统一的命令直接提取,精确的版本号可以对应唯一一个taxid,从而被精准地注释上物种学名。 4 匹配物种学名这里需要注意一个问题,blast用的nt库还有物种分类用到的两个数据库,他们的更新时间是不一致的。也就是说,物种版本号不一定能完全匹配上taxid,而taxid也不一定能匹配上学名。 而python语言写的程序,用到字典类型数据的时候,如果没有对应的key值匹配是会报错的,不会继续执行下去。一会儿解释,代码如下: 12345678910111213141516171819202122232425262728293031323334353637383940import sysimport gcsys.path.append("./")accession = open("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/filted_accession_version.txt","r")accession2taxid = open("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/nucl_gb.accession2taxid","r")taxid2name = open("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/names.dmp","r")final_res = open("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/final_res.txt","w")# 从names.dmp提取taxid和学名,匹配有scientific name的行taxid_name_dict = {} for lines in taxid2name: if "scientific name" in lines: line = lines.strip().split("|") taxid = line[0].strip() name = line[1].strip() taxid_name_dict[taxid] = name# 从nucl_gb.accession2taxid提取taxid和版本号accession_taxid_dict = {} for lines in accession2taxid: line = lines.strip().split("\\t") TAXID = line[2] VERSION = line[1] accession_taxid_dict[VERSION] = TAXID# 添加两个判断条件,版本号匹配不上taxid和taxid匹配不上学名的情况。gc.collect()释放内存。for lines in accession: line = lines.strip().split("\\t") version = line[1].split()[0] if version in accession_taxid_dict: taxid = accession_taxid_dict[version] if taxid in taxid_name_dict: final_res.write("\\t".join(line)+"\\t"+taxid_name_dict[taxid]+"\\n") else: final_res.write("\\t".join(line)+"\\t"+"INVALID TAXID"+"\\n") else: final_res.write("\\t".join(line)+"\\t"+"INVALID ACCESSION VERSION"+"\\n") gc.collect() 如果不加最后两个判断条件,程序会在报错的那行read序列终止。 通过比较两个输出结果文件行数是否一致来判断匹配是否完全。 两个文件输出结果一致说明匹配完成,为什么这里是9338而不是我们一开始blast的10000条序列呢?那是因为有662条序列balst结果的E值大于1e-5,没有在nt中比对上合适的序列 5 输出物种注释分布结果到这一步就有很多种处理方法了,可以把结果文件直接用excel打开,统计reads在nt库的分布情况和比对上的物种分布。也可以直接写个python脚本做个数据统计。 统计前我们先检查一下是否存在上一步匹配失败的reads。 123# 统计匹配失败的readscat final_res.txt | grep "INVALID TAXID"cat final_res.txt | grep "INVALID ACCESSION VERSION" 提示有8条reads的物种版本号比对不上taxid,且都是Pyrus x bretschneideri这个物种,说明这个物种还未在nucl_gb.accession2taxid这个NCBI官方数据库中更新。在结果文件中将其替换掉。 12# sed命令在原文件进行全局替换sed -i 's/INVALID ACCESSION VERSION/Pyrus x bretschneideri/g' final_res.txt 修改完成,检查无误后,用以下python脚本统计物种注释分布: 123456789101112131415161718192021222324252627from collections import Counter # 引入counter模块import syssys.path.append("./")name_file = open("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/final_res.txt","r")res_stastics = open("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/stastics.txt","w")name_list = []for lines in name_file: line = lines.strip().split("\\t") name = line[-1] # 取最后一列 name_list.append(name)# Counter()函数统计词频count_result = Counter(name_list)count_list = list(count_result.items()) # 注意需要创建一个listcount_list.append(('Unmap',662)) # 注意手动添加blast失败的序列条数到list中count_list.sort(key=lambda x:x[1],reverse=True) # 以第二维数据值,即统计的物种学名出现次数排序res_stastics.write("Name\\tHit_reads\\tpercentage\\n")for i in count_list: name = i[0] number = i[1] reads_num = 10000 percentage ="%.2f%%"%(100*float(number)/float(reads_num)) # 浮点两位小数的百分比 res_stastics.write(name+"\\t"+str(number)+"\\t"+str(percentage)+"\\n") 需要注意手动添加blast失败的序列条数,方便最后一起统计。打开生成的stastics.txt文件: 这个数据是制表符分割的,可以用excel做一个分布统计表,或者用R做一个柱状图,底下展示结果 可以看到,10000条序列比对结果占比最高的前两条序列(橘黄色的)是细菌的核酸序列,总数达到3437条。992条序列比对上罗布麻,662条序列未比对上nt库。可以认为这个序测序数据被细菌污染,可以和测序公司battle要求重新测一遍了…… 6 补充说明2022/12/4 更新秉着科学严谨的态度,再更新一些内容查漏补缺。 质控过滤后的reads有183万条,而我只取了前1万条。考虑到测序开头的低质量reads可能会对分析结果产生干扰(比如开头的电信号不稳定),我写了个python脚本对过滤后的数据随机取10000条reads,这样就只有随机误差影响分析结果了。 在第2步fq文件处理部分,为了python调用方便,先解压clean_filter.fq.gz文件 1gunzip clean_filter.fq.gz 读取解压后的文件需要49G内存,我只能在集群上处理,接着运行如下python脚本 123456789101112131415import random # 调用random模块产生随机数import linecache # 调用linecache模块读入指定行import gcoutput_file = open("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/random_test.fa","w")reads_list = list(range(1,1834926)) # 共有1834925条readsline = random.sample(reads_list,10000)for i in line: text1 = linecache.getline("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/clean_filter.fq",4*i-3) text2 = linecache.getline("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/clean_filter.fq",4*i-2) query_id = text1.split()[0] query_id_fa = query_id.replace("@", ">") output_file.write(query_id_fa + "\\n" + text2) gc.collect() 处理方式比之前多了几步,我运行了两次脚本,发现两次产生的文件大小都在135M左右,也就是随机取10000条reads产生的文件比取前10000条reads产生的文件大了40M。证明三代测序开头测得序列质量不太行(短序列不一定质量不好,但是质量不好的序列一定是短序列),拿到随机产生的10000条reads做blast,后续步骤都一样。 2023/1/13更新与测序公司技术人员沟通后,纠正一些误区: 三代测序数据是长读长片段,直接使用长读长序列进行blastn比对,能比对上数据库的概率会大很多(总有些区域能比对上一些同源序列,一条reads也可能比对上不同的物种)。 真核生物基因组含有大量的重复序列,对三代测序数据一般要进行随机打断成250-500bp大小,然后随机取一条进行blastn比对,如果这条序列刚好是重复序列片段,就会出现比对不上的结果(重复区域测序准确度也相对较低)。 因此,以上的方法更适合二代测序数据的污染评估,三代数据污染评估需要在代码上考虑实现随机打断成短片段(250-500bp),再进行blastn比对nt数据库。 总的来说,这种随机取测序read进行blastn来确定数据污染的方法,只是简单粗暴的看一下样本污染情况,如果污染比例低(通常小于1%),说明数据可以使用,还需要结合GC-depth做具体分析(只有一个红色区域说明数据无污染)。blastn比对这部分内容一般不会在文章中做展示。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"python","slug":"python","permalink":"http://www.shelven.com/tags/python/"},{"name":"blast+","slug":"blast","permalink":"http://www.shelven.com/tags/blast/"}]},{"title":"0基础学习基因组三代测序组装(1)——数据质控","slug":"0基础学习基因组三代测序组装(1)——数据质控","date":"2022-06-17T14:31:19.000Z","updated":"2022-12-03T16:14:50.000Z","comments":true,"path":"2022/06/17/b.html","link":"","permalink":"http://www.shelven.com/2022/06/17/b.html","excerpt":"最近拿到一个植物基因组的三代和二代测序数据,想通过以三代测序数据为主,二代测序数据为辅的方式学习一下如何拼接组装一个基因组。但是三代测序数据刚到手就懵了,与之前学习的转录组分析不一样,三代测序返回的几个文件不是单纯的fq文件,于是我又开始恶补了一些三代测序的基础知识,开坑写个三代基因组测序组装的系列笔记~","text":"最近拿到一个植物基因组的三代和二代测序数据,想通过以三代测序数据为主,二代测序数据为辅的方式学习一下如何拼接组装一个基因组。但是三代测序数据刚到手就懵了,与之前学习的转录组分析不一样,三代测序返回的几个文件不是单纯的fq文件,于是我又开始恶补了一些三代测序的基础知识,开坑写个三代基因组测序组装的系列笔记~ 1 技术背景当第二代高通量测序技术进入成熟阶段后,读长过短、PCR扩增带来的偏向性等问题开始日益凸显;作为基因组学上新的转折点,以PacBio单分子实时测序技术及纳米孔单分子测序技术为首的第三代高通量测序技术(Third-generation Sequencing)开始进入科研应用,从单分子水平上对DNA分子的实时测序,成功解决了二代测序几大困扰:极端 GC含量区域覆盖度低、高度重复区域无法较好地拼装、大片段变异难以准确检测、不能直接检测碱基修饰等问题。 ONT(Oxford Nanopore Technologies)牛津纳米孔测序技术作为第三代单分子实时测序技术,其原理是基于高分子膜两侧电压和其中的蛋白质纳米孔,当单分子DNA从纳米孔通过时,会引起孔两侧电位差来实现信号检测, 而ATCG四种碱基的带电性质不一样,因此利用电信号的差异就能检测出通过的碱基类型,从而实现测序。 Nanopore商业化平台有三个:MinION、GridION及PromethION。本系列笔记的三代测序数据来源于PromethION测序平台测序的一个cell,PromethION测序仪拥有48个流动槽,每个流动槽拥有3000个纳米孔通道(总计144000个),适用于大样本量的高通量快速测序。 2 数据质控basecalling在ONT的测序平台中,将通过纳米孔的DNA或RNA链产生的电位信号转化为相应的碱基序列的过程,称为basecalling。Nanopore测序的下机数据的原始数据格式为包含原始测序电信号的fast5格式,官方有提供工具Guppy进行basecalling,以mean_qscore_template的数值大于等于7为标准(也就是测序质量大于7的reads)得到原始测序数据,这样得到的basecalling数据为fastq格式(.fastq或者.fq结尾),所以我拿到的就是已经basecalling后的结果。 fast5: 原始电信号文件,以.fast5为文件结尾。此文件既有测序得到的序列信息,还有甲基化修饰信息(甲基化位点电信号会不一样)。 fastq: fast5文件转换而来,四行一个单位,序列和碱基质量一一对应。 basecalling的同时还可以一起拆分barcode条码序列,这里我没用到guppy这个软件,了解一下就行。经过basecalling后,文件会分为fail和pass两部分,pass部分就是满足Q值>7的序列(二代测序质控标准是Q20,这里的三代测序质控标准是Q7,准确性不及二代测序)。 还有一个summary.txt文件,这是一个测序汇总文件,结构如下: 第一眼看上去很乱,几个重要的列含义如下: filename_fastq fasq文件名 filename_fast5 fast5文件名 read_id 每条read对应的id号 run_id 这一次运行产生的id号,一个flowcell通常为一个run channel mux 该条read在哪个channel测的 start_time 这条read测序起始时间 duration 这条read测序经过时间 passes_filtering Q值大于7为TRUE否则为FALSE sequence_length_template read长度,三代测序数据过滤的指标之一 mean_qscore_template 非常重要的指标,每一个read的平均Q值 有关barcode的都是标签序列相关参数,因为不同样品接头会添加不同的标签序列,混测的时候根据标签序列与样品的对应关系来区分不同样品。 返回的数据是guppy处理过的,也就是raw reads,接下来质控的过程就需要自己动手了。 nanoplot质控先说明下为什么要用这个工具,三代测序的数据读长比二代测序长很多,而且每条序列的长度都是不一样的。不能用之前转录组数据分析中的fastq工具,会报错,因此使用nanoplot工具来生成质检报告,同样也是会生成各种html文件方便浏览结果。 先创建一个nanoplot专用的环境,下载nanoplot,之后的质控过程都在这个环境下进行。 12345678# 创建环境并下载nanoplotconda create -n nanoplot -y -c bioconda nanoplot# 激活环境. activate nanoplot# 生成质检报告。可以用pass.fq文件,也可以直接用summery.txt文件。-o参数后面是输出文件夹名称。NanoPlot --summary summary.txt --loglength -o summary-plots-log-transformedNanoPlot -t 4 --fastq pass.fq.gz --plots hex dot -o nanoplot_out# 详细参数设置可以在NanoPlot --help中查看 运行结束之后会生成summary-plots-log-transformed这个文件夹,我们可以用xftp工具查看里面的html结果文件,也可以挑取一些数据做数据质量统计表。 放一张原始测序数据读长分布图示意一下: 点击这里查看用summary.txt生成的质控报告 点击这里查看用pass.fq.gz生成的质控报告(推荐用这个) 两种方法生成的质控报告略微有点差别,因为summary.txt文件中记录了所有序列,可以看到有部分序列质量在Q5-Q7之间;而pass.fg.gz生成的质控报告中,所有序列的质量都在Q7及以上。后续以分析pass.fq.gz文件生成的质控报告为准,对这个文件序列的长度进行过滤。 如果不需要图,只需要知道有多少条reads、reads平均长度、N50、N90这些数据做表格的话,还有一个比较实用的perl脚本,怎么使用就不赘述了,源代码放底下参考。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354#/usr/bin/perl -wuse strict;use warnings;### *fastq.gz 数据统计 N50 N90 num_seqs sum_len min_len avg_len max_len ### usage: perl stat.fastq.gz.N50.N90.pl *.fastq.gzmy $fastq_gz = $ARGV[0];open(IN,"gzip -dc $fastq_gz|") or die ("can not open $fastq_gz\\n");open(OUT,">$fastq_gz.stat");print OUT "name num_seqs sum_len min_len avg_len max_len N50 N90\\n";print OUT "$fastq_gz\\t";my ($len,$total,$num_seqs,$min_len,$max_len)=(0,0,0,0,0);my @length_list;while(<IN>){ my $title = $_; my $seq = <IN>; my $add = <IN>; my $quality = <IN>; $seq =~ s/\\r|\\n|\\r\\n//mg; $len = length($seq); if($len>0){ $total += $len; push @length_list,$len; } if($min_len == 0){$min_len = $len;}elsif($min_len > $len){$min_len = $len;} if($max_len == 0){$max_len = $len;}elsif($max_len < $len){$max_len = $len;} $len=0; $num_seqs++;}my $avg_len = $total/$num_seqs;print OUT "$num_seqs\\t";print OUT "$total\\t";print OUT "$min_len\\t";print OUT "$avg_len\\t";print OUT "$max_len\\t";@length_list=sort{$b<=>$a} @length_list;my ($count,$half)=(0,0);for (my $j=0;$j<@length_list;$j++){ $count+=$length_list[$j]; if (($count>=$total/2)&&($half==0)){ print OUT "$length_list[$j]\\t"; $half=$length_list[$j] }elsif ($count>=$total*0.9){ print OUT "$length_list[$j]\\t\\n"; exit; }} filtlong过滤数据前面说过,二代测序是双端测序,三代测序是单端测序,两者过滤数据的要求不同。三代测序主要是过滤长度过短的序列和测序质量较低的序列。在basecalling中我们过滤了Q值小于7的序列,现在还要过滤read长度小于1000bp的序列。过滤后的序列可以直接用于后续的组装。 1234# 安装filtlong软件conda install -y filtlong# 设置序列最短为1000bp,压缩结果文件到新文件中filtlong --min_length 1000 pass.fq.gz | gzip > clean_filter.fq.gz 可以看到过滤了一部分数据,用过滤后的数据再跑一次NanoPlot 1NanoPlot -t 4 --fastq clean_filter.fq.gz --plots hex dot -o filt_nanoplot_out 测序数据读长分布如下,可以看到已经没有1kb以下的reads了: 点击这里查看过滤后的质控报告 至此质控过滤流程结束,我们可以做一个下机数据质控统计表: Type Bases(bp) Reads Number Reads mean length(bp) Reads N50 length(bp) Raw Reads 25,584,046,180.0 1,933,526.0 13,231.8 28,127.0 Filtered Reads 25,531,304,191.0 1,834,925.0 13,914.1 28,184.0","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"nanoplot","slug":"nanoplot","permalink":"http://www.shelven.com/tags/nanoplot/"},{"name":"filtlong","slug":"filtlong","permalink":"http://www.shelven.com/tags/filtlong/"}]},{"title":"NCBI的BLAST+工具本地安装,本地建库和BLAST比对","slug":"NCBI的BLAST+工具本地安装,本地建库和BLAST比对","date":"2022-06-16T18:33:56.000Z","updated":"2022-12-03T16:10:04.000Z","comments":true,"path":"2022/06/17/a.html","link":"","permalink":"http://www.shelven.com/2022/06/17/a.html","excerpt":"接触过生物学的小伙伴对NCBI在线BLAST网页一定不陌生,简单介绍一下这个网页的5种比对工具:blastn、blastp、blastx、tblastn和tblastx,以及如何进行本地建库和blast比对。","text":"接触过生物学的小伙伴对NCBI在线BLAST网页一定不陌生,简单介绍一下这个网页的5种比对工具:blastn、blastp、blastx、tblastn和tblastx,以及如何进行本地建库和blast比对。 blastn 用核苷酸序列检索核苷酸数据库 blastp 用蛋白质序列检索蛋白质数据库 blastx 核苷酸序列通过6种阅读框翻译不同蛋白序列后,检索蛋白数据库 tblastn 蛋白序列比对核酸库,核酸数据库通过6种开放阅读框翻译不同蛋白质 tblastx 核酸序列和核酸数据库都通过6种开放阅读框翻译后比对 平常我们用的最多的就是blastn和blastp,进入网页,选择blast方式,然后贴上自己的quary序列,选择数据库,选择比对的物种,设置参数如E值,wordlength长度等等。但是NCBI网站的BLAST在线工具有个让人特别无语的缺点:国内访问速度巨慢!不仅仅是比对过程慢,一条序列还好,大批量数据比对就不要想了,有的时候网页都打不开一直转圈圈。因此本地化blast工具还是很有必要的。 好在NCBI很贴心地提供了blast+工具,我们安装好blast+工具和下载好数据库以后,就可以不依赖网页和NCBI地服务器,在本地服务器上运行了。 1 安装blast+最新版blast+工具可以通过ftp方式获得,点击这里 我是在集群账户下安装,集群机器都是linux操作系统的,因此我选择的最新linux版本 别忘了要连md5校验文件一起下载,谁都不知道下载过程中是否会出错,因此大的文件下载完以后都是需要验证文件完整性的! 将上面的ftp地址拼凑一下,网速好的话可以直接用wget下载,但是我这边服务器连NCBI网速实在太慢了,wget只有10Kb/s的速度,甚至还会断开重连。看的我高血压都要犯了,无奈之下挂了个梯子,在自己电脑上下载好这两份文件,通过xftp传到了服务器上。 在服务器上首先校验文件完整性: md5sum -c ncbi-blast-2.13.0+-x64-linux.tar.gz.md5 显示结果OK后,解压: tar -zxvf ncbi-blast-2.13.0+-x64-linux.tar.gz 名字太长了,不方便以后找,顺便改个名就叫blast: mv ncbi-blast-2.13.0+-x64-linux.tar.gz blast 然后是配置环境变量: 123vim ~/.bashrc # 编辑环境变量文件# 在.bashrc文件最后一行加入如下内容(根据自己路径修改)export PATH="/public/home/wlxie/blast/bin:$PATH" 保存退出后重新source一下.bashrc文件,blast+工具就安装好了。 2 下载nr/nt数据库我们比对一般用的是NCBI的非冗余蛋白/核酸数据库,有两种方法下载nr/nt数据库: 1.通过blast+工具自带的更新程序下载 2.通过aspera工具下载 同样是网速的问题,如果用第一种方法下载,我们可以在~/blast/bin目录下找到如下的perl程序 直接运行命令 perl update_blastdb.pl nt 但是10几kb/s的速度真的让人抓狂,所以我推荐第二种方法:用IBM公司开发的快速下载神器——aspera 安装aspera在我之前写的一篇博客里推荐过sra-tools工具中的prefetch,用来下载SRA数据中存放的高通量测序原始数据。prefetch软件就是默认通过aspera工具进行下载的。 如果之前没有安装过aspera,可以用conda直接安装,命令如下: conda install -c hcc aspera-cli 这里注意下aspera-cli是aspera的命令行版本,各种不同版本的本质上下载都是调用ascp程序,并且需要openssh公钥认证,不同版本的aspera公钥文件存放的位置不同。因为我们是通过conda安装的aspera,aspera-cli公钥文件的位置在你的conda环境目录下的etc文件夹中,比如我的aspera-cli公钥文件在/public/home/wlxie/miniconda3/envs/biosoft/etc 而且因为是conda安装的,我们不需要修改什么配置文件和依赖关系,还是挺省事的。 用aspera下载数据库nr/ntnr/nt数据库也可以通过ftp方式获得,点击这里查看ftp网址 为了方便找到下载到本地的数据库,先在家目录新建db/blast文件夹,进入这个文件夹后,在当前目录下运行如下命令: ascp -QT -i /public/home/wlxie/miniconda3/envs/biosoft/etc/asperaweb_id_dsa.openssh -k1 -l 500m anonftp@ftp.ncbi.nlm.nih.gov:/blast/db/FASTA/nt.gz ./ 这里是其中一个nt数据库,nr数据库只要改一个字母就行了,两个数据库都要下载。 稍微解释下各参数的含义: -Q 用于自适应流量控制,磁盘限制所需;-T 是取消加密,否则有时候数据下载不了。两个参数是搭配一起使用的 -i 输入私钥文件,注意下载的ascp版本不一样文件位置也不一样 -k1 这里是加上了断点传续功能 -l 限制最大下载速度 后面一串是账户@ftp地址:路径。注意@和冒号。NCBI公共账号是anonftp,也就是你下载SRA数据库数据也可以用这个账号;EBI公共账号是era-fasp 最后指定下载文件的路径,我用了当前路径 可以看到下载速度杠杠的,提升了不知道多少倍…下载大数据都可以用ascp命令。 下载好之后同样别忘了校验md5文件,校验后gunzip直接解压到当前文件夹。 3 本地建库解压完成以后我们可以看到这两个数据库总大小在980G 现在还不能用这两个数据库,需要对这两个超大的数据文件建索引,也就是本地建库。 使用如下命令: makeblastdb -in nt -dbtype nucl -input_type fasta -out nt makeblastdb -in nr -dbtype prot -input_type fasta -out nr -in: 待格式化的序列文件 -dbtype: 数据类型,prot为蛋白序列,nucl为核酸序列 -input_type: 输入数据的类型,默认为fasta格式 -out: 自定义的数据库名称 这一步需要非常长时间,在目录下能看到有文件生成并且没有报错就行了,同样的操作方法可以用自己的基因组数据建库。 这里有两条核苷酸序列可能有问题,序列录到了开头第一行,不过就只有两条序列应该不影响。nt库录入了0.8亿条序列,nr库录入了4.8亿条序列。 4 创建blast全局配置文件在家目录下创建blast全局配置文件: 123456789101112131415161718192021222324252627282930313233$vim .ncbirc # 家目录下创建一个新文件.ncbirc,输入如下内容; Start the section for BLAST configuration[BLAST]; Specifies the path where BLAST databases are installedBLASTDB=/public/home/wlxie/db/blast; Specifies the data sources to use for automatic resolution; for sequence identifiersDATA_LOADERS=blastdb; Specifies the BLAST database to use resolve protein sequencesBLASTDB_PROT_DATA_LOADER=/public/home/wlxie/db/blast/nr; Specifies the BLAST database to use resolve protein sequencesBLASTDB_NUCL_DATA_LOADER=/public/home/wlxie/db/blast/ntBATCH_SIZE=10G; Windowmasker settings[WINDOW_MASKER]WINDOW_MASKER_PATH=/public/home/wlxie/db/blast/windowmasker; end of file 以上设置中定义了blastn和blastp默认的地址,这样我们在比对数据库的时候可以直接输入数据库的名称而不用给出绝对路径,方便一点(这步不是必须的,可选)。 5 运行blast程序以上准备工作完成后,准备一段query序列试一下,我的query序列名称是gene.fna 运行blastn程序: blastn -query gene.fna -out gene_blastn_nr.out -db nt -outfmt 6 -evalue 1e-5 -num_threads 10 -query: 用来查询的输入序列 -db: 指定的数据库名称 -out: 自定义输出的结果文件,最好统一格式。我是基因名_比对方法_数据库.out,这样比较直观知道比对了什么,怎么比对的 -outfmt: blast结果的呈现形式,一般用6比较多,也就是m8格式,以制表符为分隔符,有部分信息会缺失。5是XML格式比较适合解析,7在6基础上加了表头。 -evalue: 限定E值 -num_threads: 指定多少个核运行blast程序 还有其他参数比如就不一一介绍了,说明一下,一个序列的blast可以用上面的命令,多个序列的blast同样适用,把多个fasta格式的序列放进去即可。 当然,批量blast的结果需要限定匹配的结果数量,毕竟我们不可能几百上千个序列一一查看,可以指定参数-max_target_seqs 5限制每个序列的最大匹配数量,这个数值推荐是在5以上,5以下会有警告信息。 blast结果m8格式如下: 一共12列,分别能获得如下信息: 1、Query id:查询序列ID标识 2、Subject id:比对上的目标序列ID标识 3、% identity:序列比对的一致性百分比 4、alignment length:符合比对的比对区域的长度 5、mismatches:比对区域的错配数 6、gap openings:比对区域的gap数目 7、q. start:比对区域在查询序列(Query id)上的起始位点 8、q. end:比对区域在查询序列(Query id)上的终止位点 9、s. start:比对区域在目标序列(Subject id)上的起始位点 10、s. end:比对区域在目标序列(Subject id)上的终止位点 11、e-value:比对结果的期望值,解释是大概多少次随即比对才能出现一次这个score,Evalue越小,表明这种情况从概率上越不可能发生,那么发生了即说明这更有可能是真实的相似序列 12、bit score:比对结果的bit score值,越高越好","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"BLAST+","slug":"BLAST","permalink":"http://www.shelven.com/tags/BLAST/"},{"name":"aspera","slug":"aspera","permalink":"http://www.shelven.com/tags/aspera/"}]},{"title":"perl语言学习笔记(1)","slug":"perl语言学习笔记(1)","date":"2022-06-15T16:07:31.000Z","updated":"2022-12-03T16:06:51.000Z","comments":true,"path":"2022/06/16/a.html","link":"","permalink":"http://www.shelven.com/2022/06/16/a.html","excerpt":"最近在处理三代测序的下机数据,用到了一些挺好用的perl脚本,但是苦于没接触这种类型的编程语言,想根据情况改一些代码却看不懂实现方式= =","text":"最近在处理三代测序的下机数据,用到了一些挺好用的perl脚本,但是苦于没接触这种类型的编程语言,想根据情况改一些代码却看不懂实现方式= = 前面说学习perl可以只学怎么调用模块,马上啪啪打脸了,这货和python还是有点不一样的,还是抽空补补基础吧~现在做生信用的最多的就是R、python和perl,多掌握一门编程语言还是挺有必要的。记录一下自学的过程和笔记,自学视频来源是b站up主生信技能树-jimmy 示例首先了解一下perl脚本的结构,以blast结果过滤的perl脚本为例,输入文件blast_m8.out是一个12列,分隔符为空格的文件: 123456789101112131415#!/bin/perl -w # 选择解释器类型为perl,-w是运行错误时提供警告信息open IN,"blast_m8.out"; # 打开文件,m8格式输出的blastout文件。每次只读一行while (<IN>) { # 大括号为程序块,每个程序块是一个独立的部分,执行相对独立的功能 chomp; # 去掉读进来数据结尾的换行符\\n(没有换行符则不起作用) my @line=split /\\s+/,$_; # 以空白分割(\\s是匹配任何空白符,+表示匹配任意多个),存入数组中 if ($line[2] >=50 && $line[3] >=100) { print "$_\\n"; # 将第三列identity值大于50,第四列序列长度大于100的blast结果输出 } else { next; # 否则进入下一个循环 }}close IN;# 这个脚本第四行的my也要注意下,一般是需要申明use strict;也就是使用严谨的方式,在这种方式下,任何变量都必须先定义,不使用my的话定义第一次出现的$和@运行会报错。初学可以不使用use strict。# $_这个变量是使用非常多的,如果没有定义变量名称,则默认使用$_ OK,格式与python不一样,以分号作为每一行结尾,基础语法类似但不完全一样,先从基础学起。 1 标量数据标量数据特点 perl中最基本的数据类型 可以是数字、字母 无需定义类型(所有perl语言的数据都是双精度浮点型,不需要对数据类型进行定义,代价是消耗内存) “单数为标量” 字符串运算 字符串就是一连串字符组合,可以是字母数字标点等 对DNA序列处理本质上就是处理字符串 字符串可以为空 需要“引号”,尽量使用双引号 字符串连接“.”或者“x” 如 “hello” . “world” 标量变量标量变量用来动态存储标量值,以美元符号$表示(定义数组用@符号),和linux一样不能以数字开头 123456# 一个=表示赋值,两个==表示判断,这里和python是一样的$gene_num=3$gene_num=$gene_num+4$gene_num+=4 # 双目运算符,运算所需变量为两个运算符,这个简写是非常常用的$dna="ATCGGGTATCG"$dna.="ATCGGGTCG" # 双目运算符同样可以用于字符串操作,得到标量变量为连接的字符串 2 数组和列表数组构建列表(list)指标量的有序集合,数组(array)则是存储列表的变量 1234567@array=(1,"hello",undef,$dna,5); # 左边为数组,右边为列表,构建列表中间用逗号隔开$array[0]=1;$array[1]="hello" # 注意下标数字从0开始,0表示第一个元素数组的最后一个元素角标$#array,因此数组元素个数=$#array+1@number=(1..100) # 范围操作符(..)每次加一@string=qw (fred barney betty wilma dino) # qw操作符可以省略逗号 split和join函数 split将字符串根据固定的分隔符进行切割,切割后得到一个数组 join与split相反,将数组连接成一个标量 12345678#!/bin/perl$scalar="a:bcd:123:de";@array=split /:/,$scalar; # 以冒号作为分隔符分割print "@array\\n"$new_scalar=join "\\t",@array; # 以制表符作为分割符,好处是excel里打开每个元素在不同单元格print "$new_scalar\\n" pop和push函数12345#!/bin/perl@number=(1,2,3,4,5);$value=pop @number; # pop提取数组的最后一个元素push @number,6; # push添加一个元素到数组的末尾print "$value\\n@number\\n"; shift和unshift函数12345#!/bin/perl@number=(1,2,3,4,5);$value=shift @number; # shift提取数组第一个元素unshift @number,10; # unshift添加一个元素到数组的开头print "$value\\n@number\\n"; 一般来说pop和shift用的比较多,一个提取数组末尾元素,一个提取数组开头元素。 因为我们用perl处理的往往是矩阵文件,第一行是ID信息,我们往往是读入一行数据,去掉换行符,存储为标量,分割数组。这个时候就要用shift函数,将ID提取出来,这样呢后面都是同一种类型的数据,方便我们操作,也就是底下这个框架: 1234567#!/bin/perlwhile (<IN>) { chomp; my @line=split /\\s+/,$_; my @id=shift @line; print "$id\\n"} sort和reverse函数1234#!/bin/perl@number=(1..10);@number_sort=sort @number; # sort函数使数组按照ASCII码大小排序print "@number_sort\\n"; 可以看到10在1的后面,因为sort函数是以ASCII码大小进行排序的。 1234#!/bin/perl$dna="ATCGCGTAGCATCGATGCTGATCATGC";$dna_reverse=reverse $dna; # reverse函数可以使数组或者字符串反转print "$dna_reverse\\n"; reverse函数在做DNA反向互补配对的时候能用到。 foreach遍历数组123456789101112#!/bin/perl@number=(1..10);foreach $num (@number) { # 遍历数组中的值依迭代 print "$num\\n";}# 也可以省略一部分内容#!/bin/perl@number=(1..10);foreach (@number) { # 省略了$num,存储到默认的$_中 print;}","categories":[{"name":"编程自学","slug":"编程自学","permalink":"http://www.shelven.com/categories/%E7%BC%96%E7%A8%8B%E8%87%AA%E5%AD%A6/"}],"tags":[{"name":"perl","slug":"perl","permalink":"http://www.shelven.com/tags/perl/"}]},{"title":"基因组注释文件gbff格式转换gff3格式","slug":"基因组注释文件gbff格式转换gff3格式","date":"2022-06-13T17:09:45.000Z","updated":"2022-12-03T16:19:26.000Z","comments":true,"path":"2022/06/14/a.html","link":"","permalink":"http://www.shelven.com/2022/06/14/a.html","excerpt":"前几天老师布置了一个任务,寻找夹竹桃科Apocynaceae分类下的物种参考基因组,我在plaBiPD网站和NCBI的genome数据库中只找到包括罗布麻在内的5个已发表物种参考基因组,且都是gbff格式的。提交之后被告知需要gff格式的,因为gbf格式中没有基因相关结构的位置信息。找了一个perl脚本完成了任务。","text":"前几天老师布置了一个任务,寻找夹竹桃科Apocynaceae分类下的物种参考基因组,我在plaBiPD网站和NCBI的genome数据库中只找到包括罗布麻在内的5个已发表物种参考基因组,且都是gbff格式的。提交之后被告知需要gff格式的,因为gbf格式中没有基因相关结构的位置信息。找了一个perl脚本完成了任务。 emmmmm….其实不是很理解,因为gbff格式和gff格式是可以相互转换的,如果gbff注释文件中有信息缺失,那么gff格式也同样没有相关信息….不管怎么样先转换个格式交差,网上搜了下有前人写的perl脚本可以用,但是我以前没接触过perl语言,这里做个笔记写一下自己瞎捣鼓的过程。 1. perl语言和CPAN模块库从百度百科中引用对perl语言的一段描述: Perl是一种功能丰富的计算机程序语言,易于使用、高效、完整,而不是美观(小巧,优雅,简约)。同时支持过程和面向对象编程,对文本处理具有强大的内置支持,并且拥有第三方模块集合之一。 Perl借取了C、sed、awk、shell脚本语言以及很多其他程序语言的特性,其中最重要的特性是它内部集成了正则表达式的功能,以及巨大的第三方代码库CPAN。 看到这个简介,个人的理解就是perl语言就像python一样,自己有丰富的第三方库可以调用。同样,我们不需要具体了解每个模块的实现方式和底层代码,只要知道会调用相关的模块实现自己的目标即可。 CPAN是perl的一个第三方源码模块库,里面有上百万的perl模块,用于支撑perl的各种功能。为了方便安装perl的各种模块,前人做了一个CPAN模块,用cpan命令来安装perl的各个模块。也可以通过cpan命令来安装bioperl模块,里面有非常多的有关生信分析的perl脚本。 2. 安装和配置bioperl运行gbff转换gff3的perl脚本需要调用bioperl的一些模块,因此第一步需要安装bioperl,以及配置相应的环境。 登录学校集群,发现系统自带安装了perl程序和CPAN模块,我们可以用CPAN来安装bioperl。 12# 在命令行进入CPAN交互式界面perl -MCPAN -e shell 进入CPAN交互式界面如下: 1234# 在线寻找bioperl安装包cpan>d /bioperl/# 安装对应版本的bioperl,注意目录和版本号根据搜出来的结果不同而不同cpan>install S/SE/SENDU/bioperl-1.5.2_100.tar.gz 之后就是漫长的安装过程了,所有依赖关系也会一并安装,安装的时间较长,大概需要半个小时。 安装完成之后是配置环境变量,不配置的话即使安装了bioperl,也会找不到对应的模块。我的bioperl安装路径是/public/home/wlxie/miniconda3/envs/biosoft/lib/perl5/site_perl/5.22.0/Bio/,可以通过echo $PERL5LIB来查看当前perl模块的调用路径,然后在家目录的.bashrc环境变量文件中将bioperl的模块路径加到perl模块调用的路径当中。 12# 在。bashrc文件最后一行加入bioperl模块调用路径export PERL5LIB="/public/home/wlxie/miniconda3/envs/biosoft/lib/perl5/site_perl/5.22.0/:$PERL5LIB" 这样所有的配置就完成了。 3. gbff格式转换gff3github上有许多gbff格式转gff3格式的脚本代码,有用biopython做的,也有bioperl做的,可能是我配置的问题,试了几个脚本后只有一个可以顺利转换。本来想研究一下脚本的实现方式,可是源代码2000多行看的我实在不知从何下手,这里只记录一下使用方法和备份脚本文件。 源代码的出处已经找不到了…点击这里查看源代码在本站的备份 使用方法: 将脚本文件bp_genbank2gff3.pl放在要转换格式的gbff文件同一个目录下,运行命令 12# perl bp_genbank2gff3.pl gbff文件名perl bp_genbank2gff3.pl Asclepias_syriaca-GCA_002018285.1_ASM201828v1_genomic.gbff 脚本自动运行,结束后会在当前目录生成同名的gff3文件","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"bioperl","slug":"bioperl","permalink":"http://www.shelven.com/tags/bioperl/"},{"name":"perl","slug":"perl","permalink":"http://www.shelven.com/tags/perl/"}]},{"title":"集群和slurm调度系统使用心得","slug":"集群和slurm调度系统使用心得","date":"2022-05-24T18:05:26.000Z","updated":"2023-02-24T03:53:40.000Z","comments":true,"path":"2022/05/25/a.html","link":"","permalink":"http://www.shelven.com/2022/05/25/a.html","excerpt":"随着各种组学技术和生物信息学技术的发展,高通量测序现在已经广泛应用到生命科学的多个领域,这些测序数据动辄几个G甚至几十上百G(主要看物种和测序深度),个人电脑对这些大量的数据处理时有些力不从心。","text":"随着各种组学技术和生物信息学技术的发展,高通量测序现在已经广泛应用到生命科学的多个领域,这些测序数据动辄几个G甚至几十上百G(主要看物种和测序深度),个人电脑对这些大量的数据处理时有些力不从心。 比如我的小破电脑,4核,2G运行内存,在对16个10X测序深度的拟南芥转录组数据进行回帖参考基因组时,cpu满载的情况下跑了整整一个晚上,拟南芥的参考基因组还是比较小的(只有116M大小)。因此在需要做大量数据处理的时候我们往往都会用到计算机集群。 先放有用的东西:slurm官网快速开始手册 以及武汉大学测绘学院做的中文手册:点击浏览和下载 1. 计算机集群介绍先上一段百度百科的介绍 计算机集群简称集群,是一种计算机系统, 它通过一组松散集成的计算机软件或硬件连接起来高度紧密地协作完成计算工作。在某种意义上,他们可以被看作是一台计算机。 集群系统中的单个计算机通常称为节点,通常通过局域网连接,但也有其它的可能连接方式。集群计算机通常用来改进单个计算机的计算速度和/或可靠性。一般情况下集群计算机比单个计算机,比如工作站或超级计算机性能价格比要高得多。 简单来说集群就是一群电脑连接在一起完成同一个工作,自然是比单个电脑工作效率高得多。 塔大有着南疆最大的超算中心,我用测试账号登录大致看了下节点配置信息和存储规模: 可以看到塔大集群有三个分区:debug、normal和operation,其中debug是默认分区。 每个分区下都有admin91、admin92和computer1-6共计8个节点,从状态栏可以看到admin91节点处于down状态,一般来说只有故障节点才会显示状态down,但是我看了下登录节点的hostname就是admin91(admin91是登录节点),可能就是这样设置的,防止用户申请到登录节点资源(学校规定禁止在登录节点运行脚本程序,为了防止用户占用太多登录节点资源)。computer1-6都是计算节点,可以看到状态栏是idle也就是空闲的。admin92是个胖节点,状态却是drain也就是不能分配,这个我不理解,如果要运行并行命令就要用到胖节点。 裸储存容量算个大概450TB左右,不算大不算小,够用就行。 顺便看一下各个节点的性能: 稍微计算一下可以发现,计算节点computer1-6每个节点有128个核,每个核2G运行内存;登录节点admin91有64个核,每个核4G运行内存(不是很理解登录节点为什么要这么豪华……);胖节点admin92有256个核,每个核4G运行内存。总的来说,放在内地比可能确实不怎么样,但是在新疆可以说是奢华顶配了…… 2. slurm调度系统介绍塔大集群用的是slurm调度系统,简单来说就是借助slurm这个资源管理系统,将超算中心的集群计算机统一管理。slurm是个开源分布式资源管理软件,管理这种大型的计算机集群还是比较高效的,比如天河二号上就使用了 该资源管理系统。集群操作和个人电脑操作不一样的地方是,我们需要申请计算节点然后才能运行计算的命令,需要了解一下slurm的作业调度系统。 了解一下基本概念:一个分区(partition)就是节点的逻辑分组,可以有不同的节点(node);可以调用几个节点的资源创建作业步(job step),一个作业(job)就是一次资源分配,可以有多个作业步并且可以并发运行。 再来看一下slurm调度系统的组成成分: 主要有控制进程slurmcld,记账存储进程Slurmdbd(有的集群是收费的),节点监控进程slurmd,作业管理进程slurmstepd和用户命令工具组成,我们可以不用了解这些进程之间的相互关系,熟悉用户命令比如创建作业,提交作业和查看作业状态即可。 2.1 创建和提交作业slurm作业调度系统有三种模式创建和提交作业:交互模式——srun,批处理模式——sbatch和分配模式——salloc,分别介绍一下~还是再次强调一下,学校集群禁止在登录界面直接运行计算命令,第一次发现会强制终止进程,第二次管理员会注销用户账号。 2.1.1 交互模式——srun交互模式说白了就是我们通过命令行与集群产生可以互动的“交流”,具体过程如下: 通过终端提交资源分配请求,指定资源数目和限制 等待资源分配 获得资源后,自动加载计算任务 运行中,任务I/O传递终端,可与任务进行交互 任务结束后,资源被释放 一次执行srun生成一个作业步,也就是一次任务加载,执行一次最简单的hostname命令如下: -n 参数指定核数,-w参数指定节点,因此显示的hostname就是computer3。运行结束后直接释放资源。 上面的例子可能没有体现出交互的意义,因为程序比较简单。有些程序在运行的过程中需要人为调整,srun才能体现出优势。 2.1.2 批处理模式——sbatch批处理模式顾名思义特点在于需要自己写批处理脚本,具体过程如下: 编写作业脚本,指定资源数目和限制 sbatch提交作业 作业排队等待资源分配 分配到资源后在首节点加载执行作业脚本 任务结束释放资源 运行结果定向到指定文件夹 这个模式也是用的最多的,登录塔大集群可以在用户家目录下找到job_example文件夹,里面有不同的slurm脚本提供参考,举个最简单的例子sleep.slurm,我们cat一下看看脚本内容: 第一行#! 指定解释器类型,和其他脚本都一样; 第二行开始后面几行的#SBATCH都被识别为sbatch命令,因此后面都加上了对应参数,这里只有sbatch这一行会被识别成命令,注意不是被注释了。如果脚本里写了对应参数内容,命令运行脚本的时候就可以不用加入这些参数。 第六行开始也就是#以后的部分,才是我们编写脚本要运行的命令。 参考这个格式,我写了如下一个建立拟南芥参考基因组索引文件的hisat2.slurm脚本: 申请了job名为job1,一个节点,一个核,显示队列,显示程序运行开始时间,运行程序,显示结束时间。 提交直接用sbatch hisat2.slurm,很快就获得资源跑完了程序,当前目录下生成了索引文件和默认输出文件slurm-job号.out 因为输出结果在结果文件里,所以我们屏幕上是看不到运行过程的,要想监控运行过程只能在运行时输入squeue查看,后面会说。运行结束后同样会自动释放资源。 2.1.3 分配模式——salloc分配模式相比前面两个模式更灵活一些,简单来说就是申请资源,执行运算任务后手动释放资源,可以用来在正式提交sbatch前做程序测试,检查代码正确后再写成脚本提交(不过也可以直接提交,不用sbatch)。流程如下: 提交资源分配请求 排队等待资源分配 命令行执行指定的命令 命令执行结束,exit手动释放资源 这个比较简单,直接salloc后面接参数申请指定的资源就行,最后一定要exit手动释放资源。 申请后可以用hostname命令看看申请的是哪个节点,确认是计算节点后再运行计算命令。 2.1.4 作业提交参数知道了三个模式的具体用法,只需要添加对应的参数就行了。没有人会具体记住所有参数,上面只有常用的几个需要记一下。这里记录一下其他常用提交参数: 参数 含义 类型 示例 -J 作业名,squeue看到的作业名 字符串 -J wrf;表示作业名称为“wrf” -n 作业申请的总cpu核心数 数值 -n 4;表示作业申请4个cpu核心 -N 作业申请的节点数 数值 -N 1 表示作业申请1个计算节点 -p 指定作业提交的分区 字符串 -psilicon表示将作业提交到silicon分区 -t 指定作业的执行时间 数值 -t 30 表示作业的执行时间不超过30分钟 -o 指定作业标准输出文件的名称 字符串 -o %j,表示使用作业号作为作业标准输出文件的名称 -e 指定作业标准错误输出文件名称 字符串 -e %j,表示使用作业号作为作业标准错误输出文件名 -w 指定分配特定的计算节点 字符串 -w computer3 表示使用computer3节点 -d 作业依赖关系设置 字符串 -d after:123 表示本作业须待作业123开始以后再执行 顺便最后再提一下交互模式和批处理模式是可以相互结合的,脚本中可以加入srun命令,可以自行尝试。 如上,可以用sbatch一次性创建100个job,每个job运行一次srun后面的内容,也就是提交并行的100个计算作业。也可以不加–array这个参数,srun最后加上符号&在后台挂起,下一行继续用srun命令,以此来实现并行计算,不过要注意最后一行命令用wait等待所有命令运行结束后一起结束,否则读完sbatch就结束了。 2.2 其他用户命令这里再简单列举记录一些常用的用户命令和参数,方便自己后续用到时查阅~ 2.2.1 sinfo 查询信息 参数 含义 -a 查看所有分区信息(含隐藏分区) -d 查看dead状态(通信异常)的节点和分区的信息,与-r参数对应 -l 打印分区(或节点)的详细信息 -n 查看指定节点的信息 -p 查看指定分区的状态 -r 查看计算节点(内部通信)正常的节点和分区的状态,与-d参数对应 -R 查看节点不可用的原因,包括管理操作设置的异常 -t 查询指定节点状态的分区或节点的信息 –federation 显示所有集群的分区(或节点)的信息 –local 仅显示当前集群的分区(或节点)的信息 2.2.2 squeue 查询作业 参数 含义 -A 查看指定账号的作业 -a 显示所有分区(包含隐藏)下的作业和作业步 -r 按行显示作业组的每一个作业 –hide 不显示隐藏分区或者无权访问的分区中的作业 -j 根据指定的作业号查询作业信息 -l 长格式显示作业信息 –federation 显示所有集群下的作业 –local 仅仅查看当前集群的作业 -n 按作业名查询作业或作业步 -p 按分区查询作业 -s 查询作业步 -t 指定要显示的作业的状态 2.2.3 scancel 删除作业 参数 含义 <job id> 删除指定job id作业 –t 删除指定状态的作业 –account= 删除指定账号的作业 –name= 删除指定名称的作业 –partition= 删除指定分区的作业 –reservation= 删除指定预约名称的作业 –user= 删除指定用户的作业 –nodelist= 删除指定节点的作业 2.2.4 scontrol 查询详细信息这个命令和sinfo相比更为详细,主要能获得4个方面的详细信息 命令 含义 scontrol show node <name> 查询指定节点信息 scontrol show partition <name> 查询指定分区信息 scontrol show job 查询job信息,注意是所有的 scontrol show config 查询配置信息,也是所有的 2023/02/23 补充因为经常要用到scontrol show job来查看当前作业得运行情况、占用资源情况等等,因此更新一下该命令主要输出项: 参数名 解释 JobId 作业号 UserId 用户名(用户ID) GroupId 用户组(组ID) Priority 优先级,越大越优先,如果为0则表示被管理员挂起,不允许运行 Nice Nice值,越小越优先,­20到19 Account 记账用户名 JobState 作业状态。– PENDING:排队中;– RUNNING:运行中;– COMPLETED:已完成;– CANCELLED:已取消 Requeue 节点失效时,是否重排队,0为否,1为是 Restarts 失败时,是否重运行,0为否,1为是 BatchFlag 是否为批处理作业,0为否,1为是 Reboot 节点空闲时是否重启节点,0为否,1为是 RunTime 已运行时间 TimeLimit 作业允许的剩余运行时间 TimeMin 最小时间 SubmitTime 提交时间 EligibleTime 获得认可时间 StartTime 开始运行时间 EndTime 预计结束时间 Partition 队列名 AllocNode:Sid 分配的节点:系统ID号 NodeList 实际运行节点列表 BatchHost 批处理节点名 NumNodes 节点数 NumCPUs CPU核数 NumTasks 任务数 Command 作业命令 WorkDir 工作目录 StdErr 标准出错输出文件 StdIn 标准输入文件 StdOut 标准输出文件","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"集群","slug":"集群","permalink":"http://www.shelven.com/tags/%E9%9B%86%E7%BE%A4/"},{"name":"slurm","slug":"slurm","permalink":"http://www.shelven.com/tags/slurm/"}]},{"title":"转录组数据分析笔记(10)——初识GO/KEGG富集分析","slug":"转录组数据分析笔记(10)——初识GO-KEGG富集分析","date":"2022-05-15T19:47:39.000Z","updated":"2022-12-03T16:41:25.000Z","comments":true,"path":"2022/05/16/a.html","link":"","permalink":"http://www.shelven.com/2022/05/16/a.html","excerpt":"前面介绍了如何找到差异基因,我们通过R包DESeq2获得了差异表达基因,在此基础上做了更为直观的火山图和差异表达基因热图。但是仅仅知道差异表达基因的名字还不够,我们还要知道它到底有哪些功能和特征,就比如我看到一个很养眼的动漫角色,我就要去查查出自哪部番,是怎么样的人设和背景故事,一样的道理。这里简单记录下如何使用AnnotationHub,以及怎么进行GO\\KEGG富集分析。","text":"前面介绍了如何找到差异基因,我们通过R包DESeq2获得了差异表达基因,在此基础上做了更为直观的火山图和差异表达基因热图。但是仅仅知道差异表达基因的名字还不够,我们还要知道它到底有哪些功能和特征,就比如我看到一个很养眼的动漫角色,我就要去查查出自哪部番,是怎么样的人设和背景故事,一样的道理。这里简单记录下如何使用AnnotationHub,以及怎么进行GO\\KEGG富集分析。 一个基因没有注释信息,那就只是一段核苷酸序列,有了注释信息我们才能知道这个基因在染色体上的定位,在具体的某个代谢途径上发挥什么功能等等。网上能找到很多注释信息的数据库,比如模式生物拟南芥TAIR,人类基因组hg19等等,Bioconductor有一个专门用来搜集注释信息数据库的工具包——AnnotationHub。 1. AnnotationHub注释数据库搜索工具用bioconductor下载Annotationhub包,载入(注:为演示结果,以下命令均在Rstudio终端输入) 123456789101112131415161718> library("Annotationhub")> hub <- AnnotationHub()C:\\Users\\HUAWEI\\AppData\\Local/R/cache/R/AnnotationHub does not exist, create directory? (yes/no): yes |===================================================| 100%snapshotDate(): 2021-10-20> hubAnnotationHub with 62386 records# snapshotDate(): 2021-10-20# $dataprovider: Ensembl, BroadInstitute, UCSC, ftp://ftp...# $species: Homo sapiens, Mus musculus, Drosophila melano...# $rdataclass: GRanges, TwoBitFile, BigWigFile, EnsDb, Rl...# additional mcols(): taxonomyid, genome,# description, coordinate_1_based, maintainer,# rdatadateadded, preparerclass, tags, rdatapath,# sourceurl, sourcetype # retrieve records with, e.g., 'object[["AH5012"]]' 第一次使用AnnotationHub需要创建一个AnnotationHub对象。为了更直观地使用,我们将AnnotationHub对象赋值给hub变量。查看这个变量,我们可以得到如下的信息。 数据库版本是2021-10-20,目前有62386条记录 可以用$dataprovider 方式查看数据来源,比如数据来自于Ensembl,UCSC等等 可以用$species 方式查看数据库有哪些物种,比如人类、小鼠等等 可以用$rdataclass 方式查看数据类型 可以通过函数mcols()查看更多信息 获取数据的方式是object[["AH5012"]] object指你命名的变量名 以上就是AnnotationHub的标准用法,比如我想获得拟南芥的注释数据库,我就输入以下命令查找: 123456789101112131415161718192021222324> query(hub, "Arabidopsis thaliana")AnnotationHub with 13 records# snapshotDate(): 2021-10-20# $dataprovider: UCSC, PathBank, NCBI,DBCLS, FANTOM5,DLRP...# $species: Arabidopsis thaliana# $rdataclass: SQLiteFile, TxDb, Tibble, list, OrgDb, Inp...# additional mcols(): taxonomyid, genome,# description, coordinate_1_based, maintainer,# rdatadateadded, preparerclass, tags, rdatapath,# sourceurl, sourcetype # retrieve records with, e.g., 'object[["AH10456"]]' title AH10456 | hom.Arabidopsis_thaliana.inp8.sqlite AH52245 | TxDb.Athaliana.BioMart.plantsmart22.sqlite AH52246 | TxDb.Athaliana.BioMart.plantsmart25.sqlite AH52247 | TxDb.Athaliana.BioMart.plantsmart28.sqlite AH87070 | pathbank_Arabidopsis_thaliana_metabolites.rda ... ... AH91794 | wikipathways_Arabidopsis_thaliana_metabolites... AH95585 | Alternative Splicing Annotation for Arabidops... AH95951 | org.At.tair.db.sqlite AH97723 | LRBaseDb for Arabidopsis thaliana (Thale cres... AH97844 | MeSHDb for Arabidopsis thaliana (Thale cress,... query()函数查找,输入拟南芥的学名Arabidopsis thaliana我们可以看到一共找出了13个数据库。可以看到AH95951这个编号的数据库来源就是最大的拟南芥数据库TAIR(OrgDb,存储不同数据库基因ID之间对应关系,以及基因与GO等注释的对应关系,后面ID转换和GO分析要用到),我们就用这个数据库的注释资源。 1234> hub[["AH95951"]]downloading 1 resourcesretrieving 1 resource | | 0% 前面说过object[["AH5012"]]是获取数据的方式。以上,就可以挂后台自动下载了。我这里因为网速的原因不下了,从前面的基因名也能看出来,我筛选的差异基因都是AT开头的,而且之前的基因组注释文件也是TAIR下载的,我可以直接用bioconductor安装org.At.tair.db包,这里用AnnotationHub只是提供一个找注释数据库的思路。 2. GO/KEGG富集分析2.1 基因ID转换找到和下载注释数据库只是第一步,接下来GO/KEGG富集分析需要用到R包clusterProfiler和org.At.tair.db 先来看一下我们基因名是什么格式的: 很明显,我们的基因ID是TAIR类型(废话,我从TAIR下的),org.At.tair.db包可以转换基因ID类型 可以用keytypes(org.At.tair.db)或者columns(org.At.tair.db)查看可以转换的基因ID类型 转换基因ID代码如下: 12345678library("org.At.tair.db")columns(org.At.tair.db) # 查看能转换基因的ID类型diffgen <- nDEGs[, 1] # 注意只需要基因名diff_gen <- bitr(diffgen, fromType = "TAIR", toType = "ENTREZID", # 基因ID类型TAIR转换为ENTREZID OrgDb = "org.At.tair.db") # 该函数是基于org.At.tair.db包的diff_gen 这一步我的基因ID转换率只有60%左右,有将近一半的TAIR基因ID不能成功转换成ENTREZID,可能是Gene ID的版本问题,同一个基因在不同版本genecode中结果不一样,下载的注释文件原始版本我这里找不到了…暂时无法解决这个问题。只能不转换基因ID先跑一遍GO/KEGG富集分析。 看了很多教程都说clusterProfiler需要的ID类型是ENTREZID,这里我持怀疑态度,不转换后续也能得到结果,我看了函数enrichGO()默认的基因ID是ENTREZID并不代表不能改变,有可能是误传。查阅了一些资料,简单来说Entrez ID是来自于NCBI旗下Entrez gene数据库的编号系统,基因编号系统之间是可以相互转换的,这些ID可以在对应的数据库找到基因注释信息,就是说也可以在网页上手动注释。 2.2 GO/KEGG分析123456789101112131415161718192021library("clusterProfiler")# GO富集分析enrich_GO <- enrichGO(gene = diffgen, # 基因名列表 OrgDb = 'org.At.tair.db', # 输入OrgDb数据库(注释对象信息) keyType = 'TAIR', # 输入的基因名ID类型 ont = 'ALL', # 输出的GO分类 pAdjustMethod = 'fdr', pvalueCutoff = 0.05, qvalueCutoff = 0.2, readable = TRUE)GO_result <- enrich_GO@resultwrite.table(GO_result, 'GO_result.csv', sep = ',', quote = FALSE, row.names = FALSE)# KEGG富集分析enrich_KEGG <- enrichKEGG(gene = diffgen, keyType = "kegg", organism = "ath", # 输入的物种名 pvalueCutoff = 0.05, qvalueCutoff = 0.2)KEGG_result <- enrich_KEGG@resultwrite.table(KEGG_result, 'KEGG_result.csv', sep = ',', quote = FALSE, row.names = FALSE) clusterProfiler这个包进行GO和KEGG富集分析就这两个函数 这里我的GO只富集到两条细胞组分的内容: 说一下各列代表的意思: ONTOLOGY GO分类BP(生物学过程)、CC(细胞组分)或MF(分子功能) ID 富集到的GO id号 Description 富集到的GO描述 GeneRatio和BgRatio 分别为富集到该GO条目中的基因数目/给定基因的总数目,以及该条目中背景基因总数目/该物种所有已知的GO功能基因数目 pvalue、p.adjust和qvalue p值、校正后p值和q值信息 geneID和Count,富集到该GO条目中的基因名称和数目 KEGG富集分析结果表如下: ID和Description 分别代表富集到KEGG的ID和描述,其他和GO富集都类似 KEGG富集分析的时候有一点需要注意,输入的organism名称需要在官网的KEGG Organisms列表中能找到,否则是不能进行分析的!点击这里进入KEGG Organisms: Complete Genomes 还发现一个很奇怪的问题,我在官网的Organisms列表能找到拟南芥Arabidopsis thaliana,但是在上面的函数中对参数赋值organism = "Arabidopsis thaliana"会显示HTTP 400错误,也就是发出的url请求有问题,但是输入organism = "ath"程序可以正常运行,以后注意写缩写吧(应该是只有缩写才行,会通过联网自动获取该物种的pathway注释信息)。 3. 可视化clusterProfiler包还提供了GO/KEGG富集结果的可视化方案,此处代码参考CSDN,作者:Tian問 这里因为我的GO结果不好,只简单写一下流程,详细作图函数参数使用方法和效果同样可以参考上面的链接~ 12345678910111213141516171819202122232425## GO富集分析可视化#barplotbarplot(enrich_GO, showCategory = 10)#dotplotdotplot(enrich_GO, showCategory = 10)#DAG有向无环图plotGOgraph(enrich_GO) #矩形代表富集到的top10个GO terms, 颜色从黄色过滤到红色,对应p值从大到小。#igraph布局的DAGgoplot(enrich_GO)#GO terms关系网络图(通过差异基因关联)emapplot(enrich_GO, showCategory = 30)#GO term与差异基因关系网络图cnetplot(enrich_GO, showCategory = 5)## KEGG富集分析可视化#barplotbarplot(enrich_KEGG, showCategory = 10)#dotplotdotplot(enrich_KEGG, showCategory = 10)#pathway关系网络图(通过差异基因关联)emapplot(enrich_KEGG, showCategory = 30)#pathway与差异基因关系网络图cnetplot(enrich_KEGG, showCategory = 5)#pathway映射browseKEGG(enrich_KEGG, "ath03060") #在pathway通路图上标记富集到的基因,会弹出页面链接到KEGG官网 关于GO/KEGG富集分析,还有非常多的操作和应用,我只是简单做个最基础的富集分析的学习,没有涉及到手动注释,构建orgdb等等更多操作。电脑快没电了,这篇笔记先暂时记这些,以后需要用到再补充~","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"转录组数据分析","slug":"转录组数据分析","permalink":"http://www.shelven.com/categories/%E8%BD%AC%E5%BD%95%E7%BB%84%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"}],"tags":[{"name":"AnnotationHub","slug":"AnnotationHub","permalink":"http://www.shelven.com/tags/AnnotationHub/"},{"name":"GO/KEGG","slug":"GO-KEGG","permalink":"http://www.shelven.com/tags/GO-KEGG/"},{"name":"org.At.tair.db","slug":"org-At-tair-db","permalink":"http://www.shelven.com/tags/org-At-tair-db/"}]},{"title":"视频一键转字符动画——python函数封装和调用练习","slug":"视频一键转字符动画——python函数封装和调用练习","date":"2022-05-07T17:43:17.000Z","updated":"2022-12-03T16:25:24.000Z","comments":true,"path":"2022/05/08/a.html","link":"","permalink":"http://www.shelven.com/2022/05/08/a.html","excerpt":"捣鼓了几天python代码,我现在也越来越发现python的魅力所在,它的强大之处在于有非常多的第三方库可以随意调用。我不需要知道这些第三方库各种函数的实现方式,只要知道这些函数有什么作用,能得到什么结果。只要构思好自己的想法,找到对应的库就可以一步步按照我的思路编写程序,实现我想要的结果,整个构思到实现的过程让我非常愉悦~","text":"捣鼓了几天python代码,我现在也越来越发现python的魅力所在,它的强大之处在于有非常多的第三方库可以随意调用。我不需要知道这些第三方库各种函数的实现方式,只要知道这些函数有什么作用,能得到什么结果。只要构思好自己的想法,找到对应的库就可以一步步按照我的思路编写程序,实现我想要的结果,整个构思到实现的过程让我非常愉悦~ 1. 前言写这篇博客纯粹是个人爱好,也是一个巧合~ 前几天刷b站看到有人做了个剪影的字符动画,我就很好奇python是否可以实现。参考了一下github上大佬们的图片转字符画的代码,对这些代码做了点深入研究,总算搞明白了其实现方式,并且自己动手修改代码,在原有基础上改了几个bug,新增几个模块的调用,最后一步封装写成了下面这个脚本。这个脚本的功能是只要输入视频文件和你想要的视频帧率,就可以自动将视频转化为字符动画。 可以先看一下视频效果~ 或者点击这里进入b站观看 Your browser does not support the video tag. 2. 实现思路 调用ffmpeg根据帧率将视频切割成图片 调用pillow库作图,每张图片转换字符画 调用ffmpeg合并字符画并输出动画 3. 脚本代码及详解思路很清晰,接下来是写代码实现的过程,细节方面需要调用其他库 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182from PIL import Image, ImageDraw, ImageFont # pillow库作图import subprocess # 执行命令行命令,为了调用ffmpegimport sysimport os # 操作目录用import shutil # 删除目录用import numpy as np # 转化numpy数组import gc # 优化运行内存用到# 定义输入值,这个脚本需要两个输入值:file_input和FPSfile_input = sys.argv[1]FPS = sys.argv[2]def do_turn(file_input, FPS): # 调用ffmpeg切割视频 os.makedirs("tempfile/cut/") # 当前目录新建存放切割图片的临时文件夹 shell_vedio = "ffmpeg -i " + file_input + " -r " + FPS + " -qscale:v 2 ./tempfile/cut/%05d.jpg" # 按照XXXXX序号切割 shell_voice = "ffmpeg -i " + file_input + " ./tempfile/out.mp3" subprocess.call(shell_vedio, shell=True) # 切割视频 subprocess.call(shell_voice, shell=True) # 分离音频 count =0 for file in os.listdir("./tempfile/cut/"): count += 1 print("成功分离音频,截图开始转换字符画......" + "共计" + str(count) + "张") # 统计切割图张数 # 图片转换字符画 list_p = os.listdir("./tempfile/cut/") cwd = os.getcwd() os.mkdir("./tempfile/new/") # 新建存放字符画的临时文件夹 process = 1 # 统计完成转换的字符画数量 for id in list_p: # 遍历cut文件夹所有切割后的图片做字符画转换 address = str("".join(cwd + '/tempfile/cut/' + id)) # 拼接文件的绝对路径 im = Image.open(address) # 调用image打开图片 font = ImageFont.truetype("DejaVuSans-Bold", size=20) # 字体模式,可更改 rate = 0.1 # 缩放比(不调整的话像素点过多,这里统一调整) aspect_ratio = font.getsize("x")[0] / font.getsize("x")[1] # 获得字符长宽比 new_im_size = np.array([im.size[0] * rate, im.size[1] * rate * aspect_ratio]).astype(int) # 转换numpy数组,调整大小 im = im.resize(new_im_size) im = np.array(im.convert("L")) # 转换灰阶图,生成numpy数组 symbols = np.array(list(" .-vM@")) # 建立字符索引,注意要按照亮度手动排序 if im.max() == im.min(): # 全黑和全是一种颜色进行区分 if im.max() > 0: # 全是一种颜色,亮度值大于0,则全部用最亮的字符数值 im = (im / im) * (symbols.size - 1) else: im[np.isnan(im)] = 0 # 全黑时亮度值为NaN(非数值),则全部用最暗字符的字符数值,也就是全黑 else: im = (im - im.min()) / (im.max() - im.min()) * (symbols.size - 1) # 根据索引赋予相应像素点相应的数值 ascii = symbols[im.astype(int)] letter_size = font.getsize("x") # 获取字符大小,与前面一定要对应 im_out_size = new_im_size * letter_size # 这里乘以字符长宽,否则字符只有一个像素点大小 im_out = Image.new("RGB", tuple(im_out_size), "black") # 设置输出图片,背景 draw = ImageDraw.Draw(im_out) y = 0 # 两个循环穷举赋值,做字符画图片 for i, m in enumerate(ascii): for j, n in enumerate(m): draw.text((letter_size[0] * j, y), n, font=font) y += letter_size[1] # 注意+=,这里赋值字符宽度给y值 im_out.save("./tempfile/new/" + id + ".png") # 定义输出位置和图片格式 print(address + "转换成功!当前进度:" + str(process) + "/" + str(count)) # 显示进度 process += 1 gc.collect() # 重要!每次循环结束释放一次内存,否则容易内存溢出 print("转换成功!开始生成视频,请稍候......") # 调用ffmpeg合并字符画为视频,并且合并分离的音频 outvedio = "ffmpeg -r " + FPS + " -i ./tempfile/new/%05d.jpg.png ./tempfile/out.mp4" subprocess.call(outvedio, shell=True) final_vedio = "ffmpeg -i ./tempfile/out.mp4 -i ./tempfile/out.mp3 final.mp4" subprocess.call(final_vedio, shell=True) shutil.rmtree("./tempfile") # 删除临时文件夹 print("字符动画final.mp4已生成!已移除临时文件夹")# 输入格式错误则显示该条用法def usage(): print("usage:", sys.argv[0], "<file_input> <FPS>") exit(0)if __name__ == "__main__": # 封装,只有在文件作为脚本直接执行时后面的语句才会被执行,而 import 到其他脚本中后面的语句是不会被执行的 if len(sys.argv) != 3: # 判断输入的值是否为两个,没错,是判断两个 usage() else: do_turn(file_input, FPS) 4. 注意要点调用numpy模块生成数组,是因为python本身虽然可以建立多维度的数组,但是书写起来非常麻烦。numpy可以很好地解决这个问题,可以理解为能构建一个更好用的数组。在对数组进行遍历穷举,要注意两次穷举分别生成两个数组,第二次生成的数组只有一个数,所以下面draw.text第二个参数text可以用n,也可以用n[0]列举第一个数。 12345y = 0 for i, m in enumerate(ascii): # 这里i是用不到的 for j, n in enumerate(m): draw.text((letter_size[0] * j, y), n, font=font) # 第一个参数是确定坐标 y += letter_size[1] 字符大小,字符格式,以什么字符为参照,都是可以调整的。只要注意一点,我们是按照像素点的亮度来赋于这个像素点用什么字符的,所以索引列比较重要,要自己按照字符亮度排序,添加字符注意改值。 1symbols = np.array(list(" .-vM@")) # 可以改成自己想要用的字符,注意按照亮度升序 还有,在计算亮度和赋予索引值的时候,我们是按照相对亮度来计算的。因此,当图片所有像素点都是一种颜色的时候,im.max() 和 im.min()值是相等的,相对亮度会出现0/0的值,导致报错。所以我加了以下判断条件:纯色黑色和其他颜色属于两种不同情况,黑色时numpy数组亮度是非数值NaN,需要将数组全部值进行替换为亮度最小的字符的值;因为是RGB取值,其他颜色值固定在0-255之间,颜色均一,相对亮度就没有意义了,因此全部调整为最亮字符的值。 1234567if im.max() == im.min(): if im.max() > 0: im = (im / im) * (symbols.size - 1) else: im[np.isnan(im)] = 0else: im = (im - im.min()) / (im.max() - im.min()) * (symbols.size - 1) 顺便再说一个很有意思的模块subprocess,subprocess.call()函数可以执行命令行的命令,并且这个命令是在子进程实行的,只有子程序结束才会继续执行其他命令,使用起来真的特别方便!比如有些程序我的python库里没有但是我的linux里有,在python脚本的某一步我需要用到linux里的软件去处理,这个时候就可以调用subprocess.call()函数去执行linux命令行的命令了。 还有一个函数虽然不显眼,但是起着至关重要的作用 gc.collect() 没有这个函数部分运行内存不够的电脑会崩……我在这里踩了个大坑…… 在对程序进行简化以后,我以为优化地差不多了,然后发现有的时候程序会被莫名其妙killed…… vi /var/log/messages 查看运行日志,好家伙,内存溢出了 经过一番度娘,我检查了一下自己也没有用到循环引用的变量啊,那么真相只有一个了:转换字符画部分程序有3个for循环嵌套,可能是for循环引用的对象没有及时回收导致内存不断增长,最后被系统kill掉(个人猜测)。虽然python本身有垃圾回收功能,而在程序运行的时候清理地并不是很及时,引入的gc模块是python的垃圾收集器模块,与默认的回收方式算法不同,gc.collect()函数可以强制进行垃圾回收。因此我在每个转化字符画的for循环执行一次结束后强制回收内存,如下: 效果是立竿见影的,内存占用再也没超过5%了,非常的稳定! 5. 食用方法缺什么第三方库就装什么,主要是pillow库、numpy库和ffmpeg,用conda可以直接安装。上面那段脚本代码复制粘贴,保存为ascii.py,运行命令: python ascii.py <视频文件> <你想要的视频帧率> 回车,OK,静静等屏幕上的提示就好了。视频文件不在当前文件夹的话自行加上绝对路径,完成以后只会在当前目录生成一个out.mp4的输出文件。 源代码将同步上传我的github。","categories":[{"name":"编程自学","slug":"编程自学","permalink":"http://www.shelven.com/categories/%E7%BC%96%E7%A8%8B%E8%87%AA%E5%AD%A6/"}],"tags":[{"name":"python","slug":"python","permalink":"http://www.shelven.com/tags/python/"},{"name":"ffmpeg","slug":"ffmpeg","permalink":"http://www.shelven.com/tags/ffmpeg/"},{"name":"numpy","slug":"numpy","permalink":"http://www.shelven.com/tags/numpy/"},{"name":"pillow","slug":"pillow","permalink":"http://www.shelven.com/tags/pillow/"}]},{"title":"转录组数据分析笔记(9)——pheatmap绘制差异表达基因热图","slug":"转录组数据分析笔记(9)——pheatmap绘制差异表达基因热图","date":"2022-05-04T17:16:14.000Z","updated":"2022-12-03T16:30:21.000Z","comments":true,"path":"2022/05/05/a.html","link":"","permalink":"http://www.shelven.com/2022/05/05/a.html","excerpt":"前面说到怎么用ggplot做一个火山图来查看各个基因的表达情况,火山图是以log2FC值为横坐标,以-log10(FDR)值作为纵坐标,将所有的基因都做了点状图。虽然能比较直观地看到所有基因表达情况,但我们真正感兴趣的是处理后差异表达的基因。因此,我们也可以通过前面得到的表达矩阵获得差异表达的基因名,对raw count数据进行提取和均一化,然后做一个差异基因的热图,能更直观地看到差异基因在各个样本中的上调下调情况。","text":"前面说到怎么用ggplot做一个火山图来查看各个基因的表达情况,火山图是以log2FC值为横坐标,以-log10(FDR)值作为纵坐标,将所有的基因都做了点状图。虽然能比较直观地看到所有基因表达情况,但我们真正感兴趣的是处理后差异表达的基因。因此,我们也可以通过前面得到的表达矩阵获得差异表达的基因名,对raw count数据进行提取和均一化,然后做一个差异基因的热图,能更直观地看到差异基因在各个样本中的上调下调情况。 做热图我们用的最多的R包是pheatmap,可以直接用biocmanager下载。 后面所有Rstudio操作都是在同一个Rproject中进行,引用的变量如果不理解就翻前面的笔记,最后我会把一整个转录组下游分析的R流程代码写成一个文件上传到github备份。 1. 热图介绍废话不多说,先上一段热图的定义介绍:热图是用来对采集的因子响应强度或其他的一些因素进行均一化,从而利用颜色条的变化来直观地表示不同样本之间的含量变化情况的图。 定义很简单,这里我们的因子响应强度就是每个基因的raw count值,但是raw count值从0到几千上万差别非常之大,作图不方便。所以我们通常会用均一化的方法,使每个基因的raw count值变化程度处于同一个数量级,再通过不同颜色变化得到基因在不同样品的含量变化。 R自带的均一化函数是scale(),注意下scale默认的均一化方式是按列进行的,我们还可以通过函数 t() 进行矩阵的行列转化,只需要将差异基因挑出来按行(也就是基因名)进行均一化,导入pheatmap包即可做成一个最简单的热图。 2. 简化版代码先上一个最最简易版的,比如我要分析前25个最可能发生差异表达的基因,代码如下: 1234567library("pheatmap")nDEGs <- gene[which(gene$group != "NOT_CHANGE"),] # 筛选差异基因sort_DEGs <- arrange(nDEGs,padj) # 按照padj值升序排序choose_gene <- head(sort_DEGs[,1],25) # 取padj值最小的前25个基因choose_matrix <- mycounts[choose_gene,] # 从raw count矩阵中挑出这25个基因数据heat_matrix <- t(scale(t(choose_matrix))) # 转换了两次行列并均一化,实际就是按row进行了均一化pheatmap(heat_matrix) # 以默认参数做热图 在plots窗口可以预览生成的热图: 因为没有加任何参数调整,所以不好看(已经比R自带的热图函数做出来的好看了),先解释一下上面代码实现的原理。gene是我们前面做火山图的矩阵,里面已经有了我们差异基因分组的一列group nDEGs <- gene[which(gene$group != "NOT_CHANGE"),] 这个代码是将group列中字符不等于“NOT_CHANGE”的数据挑出来赋值给nDEGs,注意下赋值后的nDEGs也是矩阵,可以直接查看。 sort_DEGs <- arrange(nDEGs,padj) arrange() 函数的功能是升序排列,这里按照padj值升序排列。 choose_gene <- head(sort_DEGs[,1],25) head()函数用法不说了,取了前25个基因。注意下我们取的第一列是基因名,如果你前面已经将基因名作为rownames导入了,那就要用rowname。 choose_matrix <- mycounts[choose_gene,],返回到我们前面的raw count矩阵,将基因名对应的数据挑出来,可以看下这个时候的choose_matrix矩阵是怎么样的: heat_matrix <- t(scale(t(choose_matrix))) 先进行一次行列转换,对列数据进行均一化,再进行一次行列转换,说白了就是对每行基因的raw count数据进行均一化,得到如下矩阵: pheatmap(heat_matrix)以默认参数做热图,大功告成。 如果要对所有差异表达的基因做热图,只需要修改一下输入的矩阵就行: 123all_matrix <- mycounts[(sort_DEGs[,1]),]heat_matrix_all <- t(scale(t(all_matrix)))pheatmap(heat_matrix_all) 因为这是默认参数作图,所以输出结果非常感人: 你论文敢用这种图?所以还是需要了解一下pheatmap包的各种参数,对热图进行调整和修改。 3. 参数详解此部分内容参考CSDN博客:跳动的喵尾巴 12345678910111213141516171819pheatmap(mat, color = colorRampPalette(rev(brewer.pal(n = 7, name = "RdYlBu")))(100), kmeans_k = NA, breaks = NA, border_color = "grey60", cellwidth = NA, cellheight = NA, scale = "none", cluster_rows = TRUE, cluster_cols = TRUE, clustering_distance_rows = "euclidean", clustering_distance_cols = "euclidean", clustering_method = "complete", clustering_callback = identity2, cutree_rows = NA, cutree_cols = NA, treeheight_row = ifelse((class(cluster_rows) == "hclust") || cluster_rows, 50, 0), treeheight_col = ifelse((class(cluster_cols) == "hclust") || cluster_cols, 50, 0), legend = TRUE, legend_breaks = NA, legend_labels = NA, annotation_row = NA, annotation_col = NA, annotation = NA, annotation_colors = NA, annotation_legend = TRUE, annotation_names_row = TRUE, annotation_names_col = TRUE, drop_levels = TRUE, show_rownames = T, show_colnames = T, main = NA, fontsize = 10, fontsize_row = fontsize, fontsize_col = fontsize, angle_col = c("270", "0", "45", "90", "315"), display_numbers = F, number_format = "%.2f", number_color = "grey30", fontsize_number = 0.8 * fontsize, gaps_row = NULL, gaps_col = NULL, labels_row = NULL, labels_col = NULL, filename = NA, width = NA, height = NA, silent = FALSE, na_col = "#DDDDDD", ...) 参数内容非常之多,我这里仅挑选一些可能用得上的做个记录: 参数 描述 color 表示热图颜色,colorRampPalette(rev(brewer.pal(n = 7, name = “RdYlBu”)))(100)表示颜色渐变调色板,“n” 的数量取决于调色板中颜色的数量,“name” 为调色板的名称,(100)表示100个等级;color = colorRampPalette(c(“blue”, “white”, “red”))(100)则是通过设置三种不同的颜色进行渐变显示 scale 表示进行均一化的方向,值为 “row”, “column” 或者”none” kmeans_k 默认为NA,即不会对行进行聚类;如果想在进行层次聚类之前,先对行特征(因子)进行 k-means 聚类,则可在此调整热图的行聚类数 cluster_rows 表示仅对行聚类,值为TRUE或FALSE cluster_cols 表示仅对列聚类,值为TRUE或FALSE clustering_distance_cols 表示列聚类使用的度量方法,与行聚类的度量方法一致 clustering_method 表示聚类方法,包括:‘ward’, ‘ward.D’, ‘ward.D2’, ‘single’, ‘complete’, ‘average’, ‘mcquitty’, ‘median’, ‘centroid’ cutree_rows 若进行了行聚类,根据行聚类数量分隔热图行 cutree_cols 若进行了列聚类,根据列聚类数量分隔热图列 treeheight_row 若进行了行聚类,其热图行的聚类树高度,默认为 “50” treeheight_col 若进行了列聚类,其热图列的聚类树高度,默认为 “50” breaks 用来定义数值和颜色的对应关系,默认为 “NA” border_color 表示热图每个小的单元格边框的颜色,默认为 “NA” cellwidth 表示单个单元格的宽度,默认为 “NA”,即根据窗口自动调整 cellheight 表示单个单元格的高度,默认为 “NA”,即根据窗口自动调整 fontsize 表示热图中字体大小 fontsize_row 表示行名字体大小,默认与fontsize一致 fontsize_col 表示列名字体大小,默认与fontsize一致 fontsize_number 表示热图上显示数字的字体大小 angle_col 表示列标签的角度,可选择 “0”,“45”,“90”,“270”,“315” display_numbers 表示是否在单元格上显示原始数值或按照特殊条件进行区分标记 number_format 表示热图单元格上显示的数据格式,如 “%.2f” 表示两位小数; “%.1e” 表示科学计数法 number_color 表示热图单元格上显示的数据字体颜色 legend 表示是否显示图例,值为TRUE或FALSE annotation_row 表示是否对行进行注释 annotation_col 表示是否对列进行注释 annotation_colors 表示行注释及列注释的颜色 annotation_legend 表示是否显示注释的图例信息 annotation_names_row 表示是否显示行注释的名称 annotation_names_col 表示是否显示列注释的名称 show_rownames 表示是否显示行名 show_colnames 表示是否显示列名 main 表示热图的标题名字 gaps_row 仅在未进行行聚类时使用,表示在行方向上热图的隔断位置,如 gaps_row = c(2, 4)表示在第2与第4列进行隔断 gaps_col 仅在未进行列聚类时使用,表示在列方向上热图的隔断位置,同 gaps_row labels_row 表示使用行标签代替行名 labels_col 表示使用列标签代替列名 filename 表示保存图片的位置及命名 width 表示输出绘制热图的宽度 height 表示输出绘制热图的高度 margins 表示热图距画布的空白距离 好吧,看上去基本上都能用到。制作热图应用的统计学原理就不多说了,我也没研究明白,我们用上面的这些参数能做出好看点的图就行。所以其实pheatmap中也有对应的参数scale来对行或列进行均一化,在pheatmap中设置参数或者多加一行代码做个均一化转换都是一样的。 4. 最终流程代码知道了简化版代码的各行命令和pheatmap各参数作用,稍加一点点修改得到最终流程代码: 12345678910111213141516library("pheatmap")nDEGs <- gene[which(gene$group != "NOT_CHANGE"),]sort_DEGs <- arrange(nDEGs,padj)all_matrix <- mycounts[(sort_DEGs[,1]),]heat_matrix_all <- t(scale(t(all_matrix)))# 要做热图的每列分组,底下两行代码是必须的annotation_col <- data.frame(sample = factor(c(rep("SD",4),rep("LD1",4)))) # 简单粗暴地写一个分组矩阵row.names(annotation_col) <- colnames(heat_matrix_all)pheatmap(heat_matrix_all, color = colorRampPalette(c("blue", "white", "red"))(100), treeheight_col = 30, treeheight_row = 10, show_rownames = FALSE, angle_col = 45, annotation_col = annotation_col, filename = "test.pdf") 保存后的差异基因热图如上所示,左边4列是LD长日照1天组,右边4列是SD短日照对照组。数据不是特别好,但是两组还是能区分开的,至少也比第一次做的顺眼一点了。 标题参数main有个小bug,加了参数在plots区域预览是正常的,但是用filename保存到输出文件如果标题是中文会出错,无法显示中文标题。这个小bug倒是无伤大雅,图在plots区也可以直接保存输出,不是非得要用filname参数指定输出文件输出的。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"转录组数据分析","slug":"转录组数据分析","permalink":"http://www.shelven.com/categories/%E8%BD%AC%E5%BD%95%E7%BB%84%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"}],"tags":[{"name":"R语言","slug":"R语言","permalink":"http://www.shelven.com/tags/R%E8%AF%AD%E8%A8%80/"},{"name":"pheatmap","slug":"pheatmap","permalink":"http://www.shelven.com/tags/pheatmap/"}]},{"title":"获得测序原始数据——初探GEO和SRA数据库","slug":"获得测序原始数据——初探GEO和SRA数据库","date":"2022-05-03T20:02:37.000Z","updated":"2022-12-03T16:24:11.000Z","comments":true,"path":"2022/05/04/a.html","link":"","permalink":"http://www.shelven.com/2022/05/04/a.html","excerpt":"最近在看转录组数据分析的文献,想下载一些原始数据自己跑一跑的,发现自己对于几个高通量测序数据库还是有些不太熟悉。以我现在的经验来看,EBI数据库的原始测序数据最容易获得,可以直接在EBI官网下载需要的fastq格式文件,但是NCBI的SRA数据库下载数据还是有些麻烦的,做个学习笔记记录下。","text":"最近在看转录组数据分析的文献,想下载一些原始数据自己跑一跑的,发现自己对于几个高通量测序数据库还是有些不太熟悉。以我现在的经验来看,EBI数据库的原始测序数据最容易获得,可以直接在EBI官网下载需要的fastq格式文件,但是NCBI的SRA数据库下载数据还是有些麻烦的,做个学习笔记记录下。 初探GEO数据库和SRA数据库1. GEO数据库先上数据库链接,点击这里 GEO数据库全称Gene Expression Omnibus database,是由美国国立生物技术信息中心NCBI创建并维护的基因表达数据库。它创建于2000年,收录了世界各国研究机构提交的大多数高通量基因表达数据,GEO除了二代测序数据,还包含芯片测序、单细胞测序数据。 GEO数据库有四种数据存放类型**GSE数据编号(Series)、GPL数据编号(GEO platforms)、GSM数据编号(Samples)和GDS数据编号(Datasets)**。 一篇文章可以有一个或者多个GSE(Series)数据集,一个GSE里面可以有一个或者多个GSM(Samples)样本,如果做的是基因芯片,那每个数据集也会有自己对应的芯片平台,就是GPL(GEO platforms)。GSE编号一般为作者提交时生成的原始数据编号,后续NCBI中的工作人员会根据研究目的、样品类型等信息归纳整合为一个GDS(Datasets),整理后的数据还会有GEO profile数据,也就是基因在这次实验中的表达数据。我平常文献里看到最多的就是GSE编号,可以直接在GEO数据库的搜索框里输入查看。 我个人比较关注的是tools栏目里的FTP Site功能。ftp是文件传输协议,ftp访问的界面非常干净,如果我只需要下载GEO数据库里的文件,而不关注其他多余信息的话,可以通过这个界面非常快速地找到对应GSE编号下所有作者上传到GEO数据库的文件,目录结构层次一目了然。如下图所示,datasets对应GDS编号;platforms对应GPL编号;samples对应GSM编号;series对应GSE编号,网站还很贴心地给了README.txt里面写了帮助文档,包括怎么用ftp访问等等 在Browse Content这个栏目里,我们也能看到这个数据库的一些概览,比如现在有多少数据集,多少个芯片平台,多少上传的样品等等信息。我翻看了一下两年前jimmy出的教程,那个时候的GEO数据库收录的信息只有现在的一半不到,两年时间这些数据呈井喷式的发展,大数据时代信息发展之快也确实让我挺震惊。 尤其是点开基因芯片的平台,以上传的样品数量对这些芯片平台进行排序,发现两年前独占鳌头的Affymetrix公司的HG-U133(也是最古老的基因芯片)已经被illumina公司高通量测序芯片全面超越了接近两倍,这也意味着高通量测序在这两年发展已经不仅仅是快速发展了,简直是火箭式发展……这也是大势所趋。 GEO数据库的搜索地址也很有规律性,比如只有最后的GSE编号不同,其他网址字段都是一模一样的,这也算是方便搜索的一个没什么用处的小技巧?…… 看点有用的,我们根据文献得到的GSE编号进行搜索,可以看到如下页面,前半部分是作者的一些信息,我们可以获得测序物种、作者的联系方式、发表的文章PMID等等,而我们更需要获取的信息在后半部分: 样本信息这里解释一下,有三种主要的数据类型: SOFT 平台信息芯片中探针与基因的对应关系注释文件,样品单独的表达量,所有信息文件 MINiML XML格式的所有数据,同SOFT文件单格式不同,和HTML格式差不多 TXT 这是样品表达矩阵的数据文件,我的理解是总结类型文件,不如用底下的原始数据 原始数据信息也说一下,这里可能会给原始测序的raw data,也可能不给;有的时候会给raw count表达矩阵,也有的时候不给;样品表达矩阵也是有的时候给的raw count,有的时候给的FPKM(比如上面这种情况)。就…挺离谱的,没有标准,如果我们要跑Deseq2复现作者的结论,通过FPKM得到的表达矩阵还不能用。希望网站能制定点相关标准吧(题外话,不然对于我们这种小白,找不到数据直接痛苦面具) 我个人觉得,这个数据库比较重要的是可以获得样品分组信息和SRA数据库的链接地址,方便我们下载测序原始数据(后面介绍怎么下载)。如果不想跑原始数据,也可以直接拿样品表达矩阵来跑转录组下游分析。直接用表达矩阵就是要当心作者拿漂亮数据坑你。 2. SRA数据库同样的先上数据库链接,点这里 其实从网址上就可以看出端倪,这俩数据库都是NCBI旗下的俩兄弟,都是NCBI一个亲爹亲妈养的。我百度的时候也发现,很多人把这两个数据库混在一起,或者叫GEO/SRA数据库,其实两者还是有区别的。 SRA数据库是三大核酸数据库之一,我之前的笔记也有介绍过(点击这里查看)。我个人的理解是,SRA数据库存放的是原始测序文件,而GEO数据库存放的大部分是经过作者处理以后的数据文件(有的也包括了原始测序文件),相对而言SRA数据库更大也更存粹,而NCBI官方也给了下载SRA数据的小工具——SRA Toolkit。 SRA数据类型包含如下四种,看到前缀知道这是SRA数据库,了解一下就行: Studies 研究课题(前缀为ERP或SRP,包含多个Experiment) Experiments 实验设计(前缀为SRS,包含Sample、DNA source、测序平台和测序数据等信息) Samples 样品信息(前缀为SRX,包含一个或多个Runs) Runs 测序结果集(SSR开头的记录,代表测序仪器所产生的reads) 主要说说数据下载方式前几天也有同学问我sra数据库的原始测序数据怎么下载的,找不到下载方式,看的教程都是NCBI上直接下载的。emmmmmm我的第一反应是这不就是NCBI旗下的子数据库嘛……还能从哪儿下载… 废话不多说,直接看官网给的下载工具——SRA Toolkit 这是一个官方给的小工具合集,提供我们各种操作系统下的安装包,我把linux和windows安装包都下了。安装步骤都是一样的,解压,把bin文件夹路径加到系统环境变量,搞定。windows需要打开cmd命令行运行一次prefetch(下载命令),按照提示输入vdb-config --interactive起到类似激活的作用就行了。linux里甚至都不需要编译(也可以conda安装)。可能有的同学对自己的windows系统不熟悉,不知道怎么改自己的系统环境变量,其实这个比linux改环境变量更容易,百度一下吧= = 我个人更推荐在windows系统安装SRA Toolkit,SRA数据库本身服务器在国外,国内访问下载速度慢到令人发指(个位数Kb/s,甚至直接没有),而类似转录组这种数据,细菌可能还好点,动植物做个10X测序动不动就是几个G十几个G,加上分组和生物学重复动辄几十上百G,那点速度下到天荒地老也下不完。纯命令行的linux系统使用代理服务相对windows系统来说要麻烦一点,说白了,windows系统更容易科学上网,为了下载数据没有别的办法。 我们用的就是SRA Toolkit工具包里的prefetch命令下载原始数据,prefetch有个最大的好处是只要知道SRA数据库的数据类型编号,就可以直接下载对应的原始数据。如果要批量下载,可以将数据编号写入txt文件中再运行prefetch命令,或者直接写个循环语句。所以这里的关键是怎么得到数据编号,比如SRR编号等等。 前面GEO数据库提供了SRA数据库的链接,我们可以直接点开(或者点击原始数据底下的SRA Run Selector): 点击右上角的Send results to Run selector: 我们可以看到,这个项目一共有16个基因组测序数据,难道要16个wget命令一个一个下载麽?不需要,如果要下载的基因组数据数量多肯定不行。我们可以从Accession list里获取不同前缀的各种run数据,后面用prefetch命令结合循环语句,直接一步下载。 把Accession list的编号全部复制下来,以linux为例运行cat > id,回车后粘贴编号,按ctrl+c退出,这样就生成了一个名为id的文件,里面内容是我们要下载的基因组测序数据编号。 写一个循环语句cat id | while read id ;do prefetch $id &;done ,就可以全部下载了,这里没有指定输出目录,最终所有原始测序数据会输出到根目录下。 最后顺便说一句,下载测序原始数据的方法很多,不仅仅是官方给的这个小工具。还可以用aspera遵循一定的下载格式也可以下载原始数据,或者用最原始的wget简单粗暴直接下载,只是说这些方法都或多或少受到网络限制的影响,prefetch也只是相对稳定一点。前面的笔记我也介绍过爬虫的编程方法,分解网页结构,批量抓取我们需要的信息,这也不失为一种方法。人是活的,不要拘泥于一种思路。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"SRA","slug":"SRA","permalink":"http://www.shelven.com/tags/SRA/"},{"name":"SRA Toolkit","slug":"SRA-Toolkit","permalink":"http://www.shelven.com/tags/SRA-Toolkit/"},{"name":"GEO","slug":"GEO","permalink":"http://www.shelven.com/tags/GEO/"}]},{"title":"简易爬虫程序编程记录——以微博热搜为例","slug":"简易爬虫程序编程记录——以微博热搜为例","date":"2022-05-02T16:05:41.000Z","updated":"2022-12-03T16:23:08.000Z","comments":true,"path":"2022/05/03/a.html","link":"","permalink":"http://www.shelven.com/2022/05/03/a.html","excerpt":"写的这个小爬虫程序主要是应用requests库和lxml包的etree库,简单介绍一下。","text":"写的这个小爬虫程序主要是应用requests库和lxml包的etree库,简单介绍一下。 1. 关于爬虫百度百科对于爬虫的定义是,网络爬虫(又被称为网页蜘蛛、网络机器人,在 FOAF 社区中,经常被称为网页追逐者),是一种按照一定的规则,自动抓取互联网信息的程序或者脚本。另外一些不常使用的名字还有蚂蚁、自动索引、模拟程序或者蠕虫。 我们只需要知道爬虫的作用是抓取网页的信息,其实现在互联网上充斥着大量的爬虫,包括不局限于火车票抢票软件,各种实时数据分析网站等等,本质上都是发起大量的http请求获得信息。爬虫技术的滥用会导致目标网站在短时间内收到大量的访问请求,进而导致服务器瘫痪,相当于是ddos攻击了。但是爬虫的便利性是不可否认的,尤其是批量操作数据和获取信息,比如批量下载我们需要的文献等等。犯罪的永远是凶手而不是工具,我们在合法的范围内应用好工具,能为我们生活提供非常大的便利。 知其然知其所以然,了解这个技术的最好方法是自己去学,因此写了这个小爬虫程序。为什么拿微博热搜来练手呢,因为微博热搜网页结构非常简单明了,很容易上手…… 1.1 requestsrequests是最常用的Python HTTP客户端库,编写爬虫和测试服务器响应数据时经常会用到,专门用于发送HTTP请求。说白了requests最大的作用就是发起http请求,返回我们需要的网页数据,所谓爬虫就是从网页上抓取和整理我们需要的公开的信息,对于非公开的信息抓取是违法的。 requests请求方式: 1234567requests.get(url, kwargs): 发送GET请求requests.post(url, kwargs): 发送POST请求requests.put(url, kwargs): 发送PUT请求requests.delete(url, kwargs): 发送DELETE请求requests.head(url, kwargs): 发送head请求erquests.options(url, kwargs): 发送options请求这些请求方法的参数和用法一致,必选参数为url,其他参数为可选参数 1.2 etreelxml的etree是从上面requests返回的html源码中提取信息用的,我们可以通过xpath解析网页的dom树,从中获取我们需要的元素和内容。 主要用的也就是etree.HTML(),可以用来解析字符串格式的html文档对象,更方便对我们需要的元素和对象进行抓取,后面演示会说到。 2. 代码和结果展示123456789101112131415161718192021import requestsfrom lxml import etreeimport timeurl = 'https://s.weibo.com/top/summary/'header = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36','cookie': "UOR=www.baidu.com,s.weibo.com,www.baidu.com; SINAGLOBAL=2417808258422.6777.1651037395174; _s_tentry=-; Apache=9947874618898.105.1651493077297; ULV=1651493077318:2:1:1:9947874618898.105.1651493077297:1651037395190; PC_TOKEN=04cd3c070b; login_sid_t=80fe8e3820060c4330191a42b71357dd; cross_origin_proto=SSL; ALF=1683032497; SSOLoginState=1651496497; SUB=_2A25Pa6ZiDeRhGeFL6lAT-CzMyj-IHXVsAJCqrDV8PUNbmtB-LUjdkW9NQm3k0nVgyW6LFmyhR5luy-dtvVNK1VjC; SUBP=0033WrSXqPxfM725Ws9jqgMF55529P9D9WWTcwdUO8qo5ZVwP9-2e8C.5JpX5KzhUgL.FoMfeKzE1hz7eKe2dJLoIp7LxKML1KBLBKnLxKqL1hnLBoMNSK2EeonEeh20"}resp = requests.get(url, headers=header)resp1 = resp.content.decode(encoding='utf-8')resp2 = etree.HTML(resp1)title = resp2.xpath('//*[@id="pl_top_realtimehot"]/table/tbody/tr/td/a/text()')clout = resp2.xpath('//*[@id="pl_top_realtimehot"]/table/tbody/tr/td/span/text()')addresses = resp2.xpath('//*[@id="pl_top_realtimehot"]/table/tbody/tr/td/a/@href')print(time.strftime("%F,%R")+'\\n50条实时微博热搜\\n'+'\\n排列方式:序号+关键词+热度\\n')for i in range(51): if i == 0: print(''.join('置顶'+'\\t'+title[i]+'\\n'+'https://s.weibo.com'+addresses[i]), '\\n') else: print(''.join(str(i)+'\\t'+title[i]+'\\t'+clout[i-1]+'\\n'+'https://s.weibo.com'+addresses[i]), '\\n') 未对代码进行封装,源代码就这么十几行,实现的结果是,执行一次就在当前终端屏幕上输出实时的50条微博热搜话题,并显示序号和热度,每条热搜话题下一行生成微博超链接。 3. 代码详解建立python脚本,导入模块这步不解释了。 url是我们要抓取信息的网站地址,这个很好理解。header是我们调用requests模块需要的一个重要参数,里面提供了我们访问需要的认证信息cookie,http请求本身是无状态的,网站无法确认前一次发出请求的人和后一次发出请求的人是否为同一人,因此需要让网站记住我们的登录信息cookie以响应我们的请求。没有header可能无法返回网页信息,那这一大堆东西是怎么来的呢?需要我们审查网页元素。 3.1 获得cookie和user-agent打开微博热搜首页,登录微博,随便什么空白的地方右键,点击检查,找到network(网络)。 上面的为网页元素,日志控制台,网络,资源,性能和内存等等标签,下面的就是对应的内容,往往点击第一个总结类的文件可以获得request headers信息,这里面最重要的两个信息:cookie和User-Agent 将cookie和user-agent内容全部写到header变量中,这样每次访问网站就带上了我们唯一的标志信息 resp = requests.get(url, headers=header) 访问目标网址,返回的html源码赋值给resp,然而我们看不到返回的值是怎么样的,什么类型的,这里我就要介绍一下vscode的AREPL插件了。 3.2 AREPL查看变量和审查网站元素前面介绍vscode插件说过,AREPL可以实时打印出当前的变量信息而不需要运行代码,极大地方便了我们查看返回的值和信息,知道每一行代码发挥了什么作用。 我们看一下自定义的resp变量是什么: status_code值为200,很明显成功返回了html源码信息,但是点开来看却得不到我们需要的网页文字信息,因为还没有进行解码。我们可以看到编码方式是UTF-8,自然而然的,我们就要对resp变量值进行对应的UTF-8解码,也就是后面的代码resp1 = resp.content.decode(encoding='utf-8'),这里注意一点要用content不能用text 再来点开看看解码后的resp1: 如果有点html基础的话会发现,怎么样,是不是很熟悉!没错!这就是我们在审查网页元素获得的网页的前端结构,这里包括了所有的网页信息,再也不用点开原网站一个一个元素去找啦!(就比如我这小破站的网页元素看地我脑瓜子嗡嗡的)这里可以很轻易地看到各个节点信息,极大方便了我写上面的爬虫代码。 这里我需要三个信息,热搜的标题、热度和网址,我们来展开看一看网页结构: 像洋葱一样一层一层拨开网页结构,我们可以清楚地看到table/tbody/tr/td/a节点的内容是微博热搜标题,a这个节点的标签href就是网址,table/tbody/tr/td/span节点的内容就是热度,至此,网页结构一清二楚,我们要做的就是把信息提取出来,提取的方式就是etree解析这个字符串格式的html文档,生成对应的元素路径。 3.3 解析html文档resp2 = etree.HTML(resp1)就是用来解析字符串格式的HTML文档对象的,将传进去的字符串转变成元素对象 转换后的resp2如下,我们可以看到每个节点都被转换成了_Element对象: 接下来就是顺理成章地用xpath寻找元素路径,将对应内容提取出来,我的元素路径中应用了正则表达式,这里也不解释了。 最后可以将提取出来的三个信息一一打印出来看看是否有问题(AREPL插件真的立大功),有了信息接下来就是整理和排版,那就是print函数和循环语句的基本用法了。虽然python中的print函数和循环语句与R或者linux中略有不同,这个基础知识这里也不再赘述。 唯一我觉得需要注意的是,range() 函数提供的是0-51的整数;官网置顶的微博没有热度显示,所以我写了一个if判断语句区别;排版的时候注意转义字符,其他都是细节微调部分,怎么美观怎么顺眼怎么来。 4. 总结因为这个网页没有做反爬(或许是我没注意到)手段,至少我获取这些公开信息还是没有遇到阻碍的,这也是最最简单的一个爬虫脚本了,调用第三方模块,解析网页,最后提取信息和整理,就是这么简单也很好理解。 我最近还接触到一个明日方舟抽卡记录汇总的小程序,我看了下小程序的方法,猜测这类程序也是类似的爬虫程序。先登录官方网站,需要你输入一个网址,提供token_by_cookie这个值,这个值在network标签中能找到,并且能发现resquest url就是获取token_by_cookie值的网站。 获得这个token之后,我们可以看到抽卡记录可以通过另一个需要token的网址中直接获取,明日方舟的抽卡记录只能保存10页,因此,只需要输入token,直接更改page值1-10,就能获得详尽的抽卡信息。而做成好看的图表也无非是把爬虫程序和作图程序结合一下,封装,最后在小程序调用,思路就是这样的。 同时也能发现,如果我们提供token值给别人,除了抽卡记录以外,还能提取我们的充值信息和源石消费信息等等这类隐私信息。要掌握隐私信息无非是程序的作者想不想做的问题,毕竟还是把隐私信息握在自己手里比较好。 学习就是学习这些技术的思路并为自己所用。","categories":[{"name":"编程自学","slug":"编程自学","permalink":"http://www.shelven.com/categories/%E7%BC%96%E7%A8%8B%E8%87%AA%E5%AD%A6/"}],"tags":[{"name":"python","slug":"python","permalink":"http://www.shelven.com/tags/python/"},{"name":"爬虫","slug":"爬虫","permalink":"http://www.shelven.com/tags/%E7%88%AC%E8%99%AB/"}]},{"title":"vscode远程连接和快速搭建python环境","slug":"vscode快速搭建python环境","date":"2022-04-29T10:17:01.000Z","updated":"2022-12-03T16:17:35.000Z","comments":true,"path":"2022/04/29/a.html","link":"","permalink":"http://www.shelven.com/2022/04/29/a.html","excerpt":"最近在自学python,刚入门苦于不知道从何下手,也不知道用什么编辑器比较适合。在度娘上搜了十几款编辑器,最终决定用微软的vscode,这个编辑器可以配置Python、Java、C ++等编程环境,而且有非常强大的插件功能,界面看着也挺友好,写个日志记录下自己瞎捣鼓的配置。","text":"最近在自学python,刚入门苦于不知道从何下手,也不知道用什么编辑器比较适合。在度娘上搜了十几款编辑器,最终决定用微软的vscode,这个编辑器可以配置Python、Java、C ++等编程环境,而且有非常强大的插件功能,界面看着也挺友好,写个日志记录下自己瞎捣鼓的配置。 本来是想在我的云服务器上装vscode,但是我的云服务器上没有可视化界面……于是在我的小破笔记本上安装了vscode,后来又发现有一个插件可以ssh连接上服务器,只要能ssh连接就可以直接调用服务器上事先安装好的各种python库,真香~ 从头开始记录下使用方法和自己的设置 1. 下载vscodevscode可以直接上官网下载(速度很慢,建议科学上网),选择自己的操作系统,我用的windows 一直下一步就可以了,唯一需要注意的是把vscode加入系统环境变量中(默认选项),安装以后能够在cmd命令行通过code打开说明就改了环境变量。当然也可以在系统环境变量的path中找到,这里不赘述 2. 插件下载2.1 中文语言包英文界面对于我这种小白太难了,所以打开软件第一件事就是安装中文插件,这个在左边拓展栏输入chinese直接可以找到(我这里已经装好了,只是演示记录一下) 2.2 ssh连接插件因为我要远程调用服务器上的python库,所以我首先下载了SSH连接插件 安装以后点击右侧菜单栏的远程资源管理器,可以新建一个远程连接 按照正上方弹出的窗口提示,我们输入自己的用户账号和host地址,之后选择第一个选项,这样我们要连接的远程主机地址就被记录下来了。连接之后输入密码即可远程登录,每次登录都需要输入密码 连接以后可以选择打开文件夹,把根目录文件夹打开就可以调用远程服务器的所有文件了 看到终端成功显示欢迎界面,说明远程登陆成功,终端可以输入和执行命令了 2.3 python拓展插件远程登录只是第一步,接下来安装插件都是远程登录的窗口,安装在本地的插件一般不能用在远程登录窗口。我要搭建python环境,也是先安装python的拓展插件 2.4 AREPL插件这个插件可以在右上角点开,实时打印出你写的python脚本运行结果,变量的赋值等等,还可以检查你写的脚本哪里出错而不需要直接运行代码。这个插件在写爬虫脚本的时候真的非常方便(后面的笔记再分享,写个简单的爬虫脚本就能体会),再也不需要点开网页审查各个元素了,直接在右边框里找到 举个栗子比如我写了个打印皮卡丘的python脚本(滑稽),我不用运行程序就能在右边看到代码运行的结果 3. 脚本调试下载完插件,写完代码,我们首先要进行的就是代码调试,点击左侧菜单栏的 运行和调试 ,我们直接点击创建 launch.json文件 在弹出的正上方菜单栏选择第一个调试配置打开的python文件 本质上就是生成一个调试的json文件,不用太多了解,调试当前打开的文件就可以。也可以根据自己需要改成只调试指定名称的python脚本,改的就是红框里的部分 在脚本页面直接按F5就可以运行了,可以在代码的行号前设置红色的断点,用来分段测试代码,在写的代码比较多需要一段段检查错误的时候会比较有用。比如我在上面的皮卡丘脚本的第12行设置一个断点,再按F5运行脚本,就会从第一行报错(因为print函数被断点隔开了),右边是AREPL插件的输出结果,不受代码运行与否和断点的影响,所以能正常显示 我们同时也能看到右下角是bug调试区,也就是说我们在调试区运行程序,会自动给我们分配一个debug控制区的终端,我也可以同时在上面的bash区终端运行别的程序。 4. 格式化文档这几天自学过程中我也发现,python代码是有严格的缩进要求的,不像是linux系统中的shell语言,一行写完可以在另一行随便插几个制表符继续写下一个命令。python严格按照冒号和缩进来区分代码块之间的层次,在 Python 中,对于类定义、函数定义、流程控制语句、异常处理语句等,行尾的冒号和下一行的缩进,表示下一个代码块的开始,而缩进的结束则表示此代码块的结束。哪怕有一个多余的缩进量,就会系统报错 在赋值前后,运算符前后,#号注释之后等一些不用区分代码块层次的地方,python对空格要求却不是那么严格。虽然要求不严格,但是不小心手滑多打了或者少打了空格总归影响美观(我真的有强迫症),这个时候可以用右键的格式化文档功能,一键自动改成标准格式 比如上面这个丑不拉几的程序虽然能跑出来结果,但是强迫症看完会当场去世。这个时候可以用右键的格式化文档功能一键对齐,如下 如果点格式化文档显示的是要装autopep8拓展,那就点确认安装。这个功能就是靠autopep8这个软件实现的,但是这个拓展软件是靠pip安装的,有的人没有安装pip,或者有的人(比如我)pip有问题,一直显示ssl证书不能获取拒绝安装,改pip源也无法解决问题(又是套娃解决问题的一天),就要去github下载autopep8本地安装,更改环境变量才可以使用。也可以用conda直接一步安装,不得不说conda管理python环境变量是真的香 格式化文档也不是万能的,比方说我在父目录下封装了一个函数,我想在子目录下调用,vscode有一个缺点就是需要把当前目录加到环境变量里,然后才能调用我封装好的函数,但是!添加环境变量之后再调用,这个时候运行格式化文档的功能,系统会自动把调用模块排在添加环境变量步骤之前,因为软件的设计就是把调用模块一定放在第一位。 打个比方,我在父目录demo下封装了printpikaqiu()这个函数,这个函数作用是打印皮卡丘。文件目录如下 然后我在子目录test下调用,就需要先拓展环境变量再调用,代码如下图,f5运行没毛病,打印出一只皮卡丘: 但是右键格式化文档之后,代码直接变了,再运行直接红色的报错跳脸上 因为代码顺序改了,没有添加环境变量,找不到父目录demo怎么可能调用pikaqiu模块呢?这就非常尴尬了 因此在调用自己封装的函数和模块的时候,不要用格式化文档。现在暂时还没找到可靠的解决方法 5. 其他设置vscode主菜单栏 文件—首选项 底下有非常多非常详细的设置选项,而且可以不同设备进行同步,这个是其优点之一。并且可以通过ctrl + shift + p 快速打开设置,支持直接修改配置的json文件,这个暂时还没用到,我只是改了个字体大小,以后有重要修改的时候再做记录,方便后续查看。 顺便提一嘴,我的云服务器是安装了anaconda管理python环境的,用vscode远程ssh登录云服务器后,仍然可以在右下角选择我用anaconda安装的各个版本python,依然是一键切换环境,真的太方便了。之前没用编辑器码代码,每次都要在linux里通过vim码好保存退出,再运行。。。各种意义上的身心折磨。。。 现在就一个字,香!","categories":[{"name":"编程自学","slug":"编程自学","permalink":"http://www.shelven.com/categories/%E7%BC%96%E7%A8%8B%E8%87%AA%E5%AD%A6/"}],"tags":[{"name":"vscode","slug":"vscode","permalink":"http://www.shelven.com/tags/vscode/"},{"name":"python","slug":"python","permalink":"http://www.shelven.com/tags/python/"}]},{"title":"转录组数据分析笔记(8)——ggplot2和ggrepel绘制火山图","slug":"转录组数据分析笔记(8)——ggplot2和ggrepel绘制火山图","date":"2022-04-24T20:53:27.000Z","updated":"2022-12-03T16:32:02.000Z","comments":true,"path":"2022/04/25/a.html","link":"","permalink":"http://www.shelven.com/2022/04/25/a.html","excerpt":"主要介绍下在用DESeq2得到我们想要的差异表达基因后,如何在R中用ggplot和ggrepel绘制火山图。","text":"主要介绍下在用DESeq2得到我们想要的差异表达基因后,如何在R中用ggplot和ggrepel绘制火山图。 1. 前言前面介绍了怎么用DESeq2做两组样本的差异基因表达分析,以及怎么用dplyr包给DESeq2运行结果增加一列分组信息,我们先看下两个R包运行结束后生成的gene_0_1.csv文件是怎么样的: A-G列结果是DESeq2跑的,我们用到的只有基因名,log2FoldChange和padj这三列,通过log2FoldChange绝对值大于1,调整后的pvalue也就是padj(即FDR值)小于0.05筛选除差异表达基因,最后加上group列方便查看。 到这里已经可以通过排序找到我们感兴趣的基因了,但是这样的数据不够直观,我们还可以用最著名的绘图R包ggplot2做个火山图。这里需要准备的绘图R包是ggplot2,还有添加标签的R包ggrepel。 1.2 两个注意点什么是火山图就不多bb了,重要的是知道我们可以从火山图获得两个信息:差异表达倍数(FoldChange值)和统计学显著性的标志p值。为了更方便比较和作图,我们一般用log2FC代替Fold Change值并作为X轴数据,表示两样品(组)间表达量的比值,对其取以2为底的对数即为log2FC,一般默认取log2FC绝对值大于1为差异基因的筛选标准;用FDR(也就是padj值)代替pvalue,并取-log10(FDR)值作为Y轴,FDR是错误发现率,是pvalue值进行校正得到的。 log2FC有正有负很好理解,可能有同学发现,有的基因明明有pvalue值,但是校正之后的FDR值却是NA,如下: 查阅了一些资料,当基因的在所有样本中表达量为0,则两个值都为NA;当read count数较低时,DESeq2进行Independent Filtering过滤了一部分可能造成假阳性的结果,此时padj值为NA。因此,这部分数据在做火山图的时候因为没有对应的Y值也会被过滤掉。 2. 流程代码这部分需要一点R语言基础,需要知道怎么改自己的参数。假设前面没有用dplyr包做差异筛选(做了也不影响,只是多一列数据而已)只用DESeq2跑了个结果,我们同样可以用ggplot2包做筛选,用ggrepel包美化做标签。继续用前面DESeq2生成的csv文件,流程代码如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748library("ggplot2")gene <- read.csv("gene_0_1.csv", stringsAsFactors = FALSE)gene[which(gene$log2FoldChange <= -1 & gene$padj < 0.05), "GROUP"] <- "DOWN"gene[which(gene$log2FoldChange >= 1 & gene$padj < 0.05), "GROUP"] <- "UP"gene[which(abs(gene$log2FoldChange) < 1 | gene$padj >= 0.05), "GROUP"] <- "NOT CHANGE" # |代表或,和linux里的管道是完全不一样的。以上三步新建了一列GROUP,筛选并赋予了三个值。res <- ggplot(gene, # ggplot数据来源,这里省略了data = 和mapping = aes(x = log2FoldChange, # 表示映射关系,就是定义xy y = -log10(padj), col = GROUP)) + # 注意这里定义颜色用col,以GROUP值区分 geom_point(alpha = 0.5, # ggplot做散点图,设置点透明度和大小 size = 1) + scale_color_manual(values = c("red","blue","grey"), limits = c("UP","DOWN","NOT CHANGE")) + # 自定义颜色 theme(panel.grid = element_blank(), # 去网格线 panel.background = element_rect(color = "black", fill = "transparent"), # 去背景色,透明 plot.title = element_text(hjust = 0.5), # 调整图标标题位置为中间 legend.key = element_rect(fill = "transparent"), legend.background = element_rect(fill = "transparent"), legend.position = "right") + # 设置legend图标 geom_vline(xintercept = c(-1, 1), color = "gray", size = 0.3) + # 设置x轴辅助线 geom_hline(yintercept = -log10(0.05), color = "gray", size = 0.3) + # 设置y轴辅助线 labs(x = "log2 Fold Change", y = "-log10(FDR) ", title = "LD 1 day vs LD 0 day") # 设置坐标轴标题和火山图标题res # 查看结果,plots中可以查看library("ggrepel")up <- subset(gene, GROUP == "UP") # subset从数据框中筛选符合条件的数据up <- up[order(up$padj), ][1:5, ] # order升序排序,取前5个down <- subset(gene, GROUP == "DOWN")down <- down[order(down$padj), ][1:5, ]resdata <- res + geom_text_repel(data = rbind(up, down), # ggrepel特有的函数 aes(x = log2FoldChange, y = -log10(padj), label = X ), # label值指定哪列做标签 size = 3, box.padding = unit(0.5, "lines"), segment.color = "#cccccc", show.legend = FALSE) # 以上都是特有参数resdata # 查看结果ggsave("gene_0_1.png", resdata, width = 10, height = 10) # 输出结果文件 3. 代码详解3.1 ggplot2123gene[which(gene$log2FoldChange <= -1 & gene$padj < 0.05), "GROUP"] <- "DOWN"gene[which(gene$log2FoldChange >= 1 & gene$padj < 0.05), "GROUP"] <- "UP"gene[which(abs(gene$log2FoldChange) < 1 | gene$padj >= 0.05), "GROUP"] <- "NOT CHANGE" 之前这里稍微解释一下,即使前面没有用dplyr包,用别的方法同样可以筛选差异基因并且新增一列分组数据,万变不离其宗,核心的判断方式是一样的。如果前面学了linux,注意最后 | 这个符号在R里表示或,不要和管道混淆。 123456ggplot(data = 输入的数据, mapping = aes(x = 定义值, y = 定义值, 其他参数)) + genom_point(参数) + # 选择作图方法和参数 其他设置函数和参数 # 可有可无,美观相关的东西 我总结了一下ggplot最基本的结构,data和mapping是缺省值,可以不写。 输入的数据可以是表格,可以是数据框等等;aes自定义点的映射范围,大小,颜色等等;作图方法有很多,比如点状图是genom_point。自由度很高,能设置的东西也非常之多,只有两点需要注意,同一个函数不同参数用 , 隔开;不同函数用 + 隔开。 中间的设置函数也稍微解释一下: scale_color_manual() 该函数是R中的一种自定义配色方法,手动把颜色赋值给参数value。我这里将UP的点赋予了红色,DOWN的点赋予蓝色,其他点赋予灰色。 theme() 该函数与主题配置有关,参数非常多,可以选取需要的比如背景色、网格线等等进行设置。这里举个例子,legend是图标,在ggplot中legend有四部分: legend.tittle, legend.text, legend.key和legend.backgroud,而每一个部分都有四种函数可以嵌套(也是是对应4种处理方式):element_text()绘制标题相关;element_rect()绘制背景相关;element_blank()空主题,对元素不分配绘图空间;element_get()得到当前主题的设置。每个函数还有相应的参数,说起来就没完没了了。。。常用的设置知道就行。 geom_vline() 和 geom_hline() 这两个函数分别设置x轴y轴辅助线,目的是使我的火山图更直观,从图上可以直接看到我的分类依据。 labs() 该函数自定义x轴y轴标签和图标标题。这里提一嘴,火山图标题也是一个注意点,一般是 处理组vs对照组 ,因此前面也说到DESeq2处理数据要注意顺序问题,不然会得出完全相反的结论,在火山图上的表现为与实际火山图呈镜像对称,这也很好理解。 这里看一下res结果,我们可以在plots中看到缩略的预览图: 3.3 ggrepel在过滤掉4000多个FDR值为NA的点,我们获得了一个不怎么像火山的火山图 (简直丑爆了) ,但是数据还是挺美丽的,上调区域和下调区域都有前后对比差异非常大的基因:log2FC绝对值越大,差异越明显;-log10(FDR)值越大,可信度越高。 但是这个图还有个缺点,我不知道这些代表性的差异点对应什么基因名,我还要回到excel里去筛选排序。因此,我推荐用ggrepel包对火山图进一步美化,加上基因标签,能一眼看到我感兴趣的基因。 这个包的原理和发展咱就不说了,已经是半夜4点了。。。简单介绍下中间用到的函数的结构。 subset() 从数据框中筛选符合条件的数据,我将UP的点和DOWN的点都提取出来。 order() order是升序排序,因为上调和下调的基因都比较多,全部打上基因名标签是不现实也没有意义的。我按照padj列也就是FDR值进行升序排序,取前5个可信度最高的基因打上基因名标签。当基因较少的时候是可以全部打上标签的。 在前面ggplot2作图的基础上,我们加上geom_text_repel()这个特殊的ggreple包函数,这个函数是基于函数geom_label()做的改良,它将标签置于一个方框中,并且每个标签有算法优化不会重叠。该函数的结构与前面的ggplot前半部分类似: 12345geom_text_repel(data = 输入数据, aes(x = 定义值, y = 定义值, label = X ), 其他参数 这里所有参数设置都是平行的,所以只需要 , 隔开。 我之前说到提取了up和down的数据,这里我把它们rbind一下合在一起,就形成了新的数据框数据,也就是我只对前面排序筛选的上调下调各5个基因打标签。这里注意下aes()这里的 label 是指定标签的,也就是我们这里的基因名,应该用的行名才能和数据一一对应,这里我用X是偶然发现的一个很有趣的事: 前面导入gene数据框的时候,自动把行名加到了第一列成了单独的一列,且该列列名系统定义为X,我们可以进入environment找到gene点开看看这个数据框结构,如下所示: 因此这里直接用label = X就能完美解决问题。反而我回头用row.names = 1用第一列做行名修改了gene数据框的读取方式,再在这里用label = rowname(gene)会提示长度错误或者不匹配,个中原因我暂时还没想明白。 其他特有参数就解释一下我用到的几个: size: 标签大小 box.padding: 标签连接方式,我用了线 segment.color: 线段颜色,可以用RGB颜色代码 show.legend: 是否显示标签的标签 =_=好像有点绕,说白了就是要不要再图标上再打标签… 同样放一张plots里的缩略图,是不是要直观一点了呢? 3.4 结果输出ggsave()是ggplot2包里的输出结果的函数,自定义输出的文件类型,比如pdf、png等等,还可以自定义输出图片大小,这里不赘述,主要放一个完成图看看和plots里的缩略图做个比较。完成图如下: 可能还是有些不美观,但是这些数据很不错,极端点偏离X轴和Y轴较远,都是我们需要重点关注的基因。 如果我们记下了这几个极端点,我们还可以通过在ggplot中限制X轴和Y轴范围比如xlim(-10, 10) + ylim(0, 14),再次缩小范围,得到一个更像火山的火山图 (没有意义,纯粹吃饱了撑的) 如下:","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"转录组数据分析","slug":"转录组数据分析","permalink":"http://www.shelven.com/categories/%E8%BD%AC%E5%BD%95%E7%BB%84%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"}],"tags":[{"name":"R语言","slug":"R语言","permalink":"http://www.shelven.com/tags/R%E8%AF%AD%E8%A8%80/"},{"name":"ggplot2","slug":"ggplot2","permalink":"http://www.shelven.com/tags/ggplot2/"},{"name":"ggrepel","slug":"ggrepel","permalink":"http://www.shelven.com/tags/ggrepel/"}]},{"title":"linux操作指令总结整理","slug":"linux操作指令总结整理","date":"2022-04-20T15:44:49.000Z","updated":"2022-12-03T16:10:22.000Z","comments":true,"path":"2022/04/20/b.html","link":"","permalink":"http://www.shelven.com/2022/04/20/b.html","excerpt":"该篇内容非常之多,主要记录自己能用的上的linux操作指令和自己的一些理解,想要用的时候方便站内搜索直接查找。","text":"该篇内容非常之多,主要记录自己能用的上的linux操作指令和自己的一些理解,想要用的时候方便站内搜索直接查找。 1. linux常用命令cdcd:Change directory修改(进入)工作目录,只对目录文件有效 12345cd / 进入根(root)目录cd - 返回上次的目录cd 返回家(home)目录cd ~ 返回家目录cd .. 返回上一级目录 ls ls:List filesls计算不了目录内文件大小,所以显示的目录大小不是实际的,要看目录实际大小用du命令 123456-a 列出包括.a开头的隐藏文件的所有文件-A 通-a,但不列出"."和".."-l 列出文件的详细信息-c 根据ctime排序显示-t 根据文件修改时间排序-h 将文件大小按照易于读懂的方式显示(多少M,多少G) ll和ls-l是同样的用法,linux可用,mac中不能用,可以改环境变量文件自定义ll用法 pwdpwd:print working directory,打印当前所在目录 cpcp: Copy file拷贝并粘贴文件,并且可以重命名 12345-b 覆盖前做备份-f 如存在不询问而强制覆盖-i 如存在则询问是否覆盖-u 较新才覆盖-t 将多个源文件移动到统一目录下,目录参数在前,文件参数在后 $ cp ../data/xist.fa xist_seq.fa # 复制上一个目录data目录下的xist.fa到当前目录,并重命名为xist_seq.fa$ cp -r 003/ 007 # 递归的方式,复制003目录到007目录,目录复制到目录要用递归 mvmv: Move file移动文件,相当于windows下的剪切粘贴,如果剪切粘贴到同一目录下,则为重命名 12345-b 覆盖前做备份-f 如存在不询问而强制覆盖-i 如存在则询问是否覆盖-u 较新才覆盖-t 将多个源文件移动到统一目录下,目录参数在前,文件参数在后 $ mv a1.index.sh ../ # 移动到上一目录$ mv a1.index.sh a2.index.sh # 重命名为a2.index.sh$ rename txt doc * # 把所有txt改成doc,批量文件重命名可以用rename rmrm: Remove file删除文件 1234-r 删除文件夹(就是删除目录)-f 删除不提示-i 删除提示-v 详细显示进行步骤 一定要慎重使用,命令行模式下删除文件不可恢复$ rm -rf *.fna #删除目录下所有以.fna结尾的文件 lnln: Link files创建连接文件,包括软连接和硬链接,一般软连接比较常用,相当于windows下的快捷方式;硬链接相当于重要文件的备份,默认硬链接删除原文件,硬链接文件不受影响,软连接文件则无效 12-s 建立软连接 -v 显示详细的处理过程 mkdirmkdir:Make directory创建文件夹(目录) 123-p 递归创建目录,若父目录不存在则依次创建-m 自定义创建目录的权限-v 显示创建目录的详细信息 $ mkdir rnaseq #创建一个名为rnaseq的目录 touch建新的空文件(可写入的文件)$ touch 1.txt 2.txt 3.txt # 同时新建三个文件,一个文件可以直接vim建立 catcat: concatenate 连接cat的一个作用是查看文件,一般是比较小的文件,行数小于一个屏幕,最多不要超过两个屏幕,否则会刷屏(屏幕输出的方式)cat另一个作用是合并多个文件,一般配合重定向合并为一个新文件或者将一个文件内容追加到另一个文件结尾$ cat a1.txt a2.txt >all.txt # 合并文件,并不会删除原文件,覆盖新文件内容,新文件为all.txt$ cat a1.txt >>a2.txt # 同样是合并,a1重定向到a2结尾$ cat >id.txt # 回车输入内容,可新建id.txt文件,ctrl+c退出 echo不可以这样新建,只能echo "内容">id.txt 1-A 显示文件内的空白信息 linux系统下是换行\\n;mac系统下是回车\\r;windows系统下回车加换行两个字符\\r\\n 三者都是空白,用less无法看出区别,只能在cat -A下看到不同操作系统的换行符信息 less / moreless和more都是文件查看工具,但是less功能更多一些,在windows系统下打开一个10G的文件比较困难,但是在Linux下非常方便,less可以打开非常大的文件,压缩格式也可以直接打开。注意后面接文件,不能接目录。 12-m 显示类似于more命令的百分比-N 显示行号 more:q退出,space向下翻一页,enter向下滚动一行,b往前翻一页,会加载全部显示浏览到百分之几,退出后会加载显示的所有内容less:类似,还可以用pageup和pagedown,不会加载全部,退出后不会加载文件内容显示到当前界面less下按h进入帮助界面;按/向下搜索字符串,按?向上搜索字符串,搜索状态下n和p前后跳转;按v进入编辑 head / tail这两个命令比较简单,只是取一个文件的头部和尾部多少行,默认10行,可以加-n进行设置,利用管道可以取文件中间行$ head -40 a.txt | tail -20 #取文件第21~40行$ tail -n +20 notes.log #取文件的第20行到文件末尾 g(un)zip/ b(un)zip2gzip和bzip2是文件压缩工具,默认直接对源文件进行处理,压缩比率在2/3左右,都可以进行设置加上un,为unpack的意思,表示解压缩linux压缩文件格式是.gz和.bz2windows压缩文件有.rar文件,可以下载rarlinux工具解压缩;.zip文件可以通过unzip命令解压bzip2压缩比更高(尽量下载bz2压缩文件),但是占用更多CPU$ gzip a.txt # 压缩a.txt文件$ gunzip a.txt.gz # 解压a.txt.gz文件压缩的文件可以用less或者zcat打开文件 tar(很多生物软件是打包并压缩的)tar:Tape archive (磁带档案)tar主要用于打包,由于tar能调用gzip或者bzip2进行压缩,而打包和压缩经常如windows系统一样合并为一个过程 123-c 建立打包档案,可搭配 -v 来察看过程中被打包的档名(filename)-t 察看打包档案的内容含有哪些档名,重点在察看文档名就是了(同less功能)-x 解打包或解压缩的功能,可以搭配 -C (大写) 在特定目录解开 以上三个命令不能同时使用,只能三选一辅选项: 1234-j 透过 bzip2 的支持进行压缩/解压缩:此时档名最好为 *.tar.bz2-z 透过 gzip 的支持进行压缩/解压缩:此时档名最好为 *.tar.gz-v 在压缩/解压缩的过程中,将正在处理的文件名显示出来!-f filename -f 后面要立刻接要被处理的档名!f很重要,每次执行tar命令都要加上 对于初学者,记住c是creat,创建,x是解包,z对应gzip,j对应bzip2即可,所以常用的命令如下:$ tar -jcvf filename.tar.bz2 A B C #打包压缩为bz2结尾文件$ tar -jxvf filename.tar.bz2 # 解压缩.tar.bz2结尾文件$ tar -zcvf filename.tar.gz A B C #打包压缩为gz结尾文件$ tar -zxvf filename.tar.gz # 解压缩.tar.gz 结尾文件$ tar -jxvf filename.tar.bz2 -C 目录名 #解压缩到指定目录,注意是大写Cless命令可以不解压只查看(真的强大),tar -tf filename同样如果只需解压其中一个文档,可以先通过-t查看文档名并复制,再在前面解压缩的命令基础上加空格和文档名 wcwc = Word Count统计一个文件中,行数,单词数(有空格或者换行符的字符串),字符数 1234-l filename 报告行数-c filename 报告字节数-m filename 报告字符数-w filename 报告单词数 统计当前目录下有多少文件$ ll | wc # 注意显示行数比实际多两行,因为还有隐藏的当前目录.和上一层目录.. 可通过ls -a查看 sort排序,默认按第一列排序,可以通过-k进行设置;默认排序规则为按ASCII码排序,可以通过-n进行修改;-r取相反方向; 12345-n 依照数值的大小排序。-o 将排序后的结果存入指定的文件。-r 以相反的顺序来排序。-t 指定排序时所用的栏位分隔字符。-k 选择以哪个区间进行排序。 $ sort -nk2 -k1 01.txt | less # 在01.txt文件中,根据第二列数字大小进行排序,数字一样的比较第一列并排序 uniq用于检查及删除文本文件中重复出现的行列,一般与 sort 命令结合使用,排序之后使用uniq 1234-u 显示未重复的行-c 统计重复行的数量(在行首标注)-ci 忽略大小写统计重复行-d 显示重复出现的行 # cut -f 1 blast.out | sort -t "|" -nk2 | uniq | wc -l #从blast.out文件中提取第一列(f代表字段),第一列字段以“|”分割并比较第二段的数字大小进行排序,去除重复行,并记录行数 即记录有多少条比对上的基因 dfdf: disk freedf用于查看磁盘消耗,显示磁盘可用空间数目信息及空间结点信息。一般加一个-h选项,然后接要查看的磁盘,默认所有磁盘。 12345-a 显示全部文件系统-h 文件大小友好显示-l 只显示本地文件系统-i 显示inode信息-T 显示文件系统类型 dudu: Disk usagedf用于查看磁盘使用情况,du用于查看目录所占磁盘大小,一般也加-h选项 12-h 方便阅读的方式(显示带单位)-s 只显示总和的大小 findfind顾名思义,主要用于查找文件。因为当文件越来越多的时候,由于Linux是文本界面,不方便可视化文件,这个时候就可以利用find快速找到需要的文件。find支持多种搜索方式主要用的搜索方式:find 目录 Expression 条件$ find /media/ -name *.fna #查找media目录下所有.fna结尾的文件$ find /media/ -size 100M #查找media目录下所有大于100M的文件 which$ which filename # 查看可执行文件的位置,在PATH变量指定的路径中查看系统命令是否存在及其位置 whereis该指令只能用于查找二进制文件、源代码文件和man手册页,一般文件的定位需使用locate命令 locate是find -name的另一种写法,但是要比后者快得多,原因在于它不搜索具体目录,而是搜索一个数据库/var/lib/locatedb,这个数据库中含有本地所有文件信息。Linux系统自动创建这个数据库,并且每天自动更新一次,所以使用locate命令查不到最新变动过的文件。为了避免这种情况,可以在使用locate之前,先使用updatedb命令,手动更新数据库 toptop可以动态显示(3s一次)系统进程使用情况,类似于windows系统的任务管理器。可以显示当前系统正在执行的进程的相关信息,包括进程ID、内存占用率、CPU占用率等。 psps: process statusps也是系统进程管理工具,与top不同的是,top可以动态显示,而ps则是静态显示,是某一时刻的快照,静态显示的好处是便于其他程序捕获结果,进行处理。 killkill的作用是杀死进程,给定一个任务的PID号,可以通过top或者ps命令获得,例如当前有一个sleep进程,pid号为12000;通过kill -9可以强制杀死$ kill -9 12000 12345671 终端断线2 中断,相当于ctrl+c2 退出,同ctrl+\\9 强制终止15 终止进程,默认为1518 继续,与STOP相反,fg/bg命令19 暂停,同ctrl+z chmodchmod: Change mode用于修改文件权限,Linux基础权限可以包括ugo模式(文字设定法)以及421模式(数字设定法),可以用通配符一次修改所有类型的文件文字设定法:u表示属主(user),g表示同组群用户(group),o表示其他用户(other),a表示所有用户(all) 123456+ 添加权限- 删除权限= 赋予给定权限,并取消其他所有权限r 可读(read)w 可写(write)x 可执行(execute) 数字设定法: 12340表示没有权限,1表示可执行权限,2表示可写权限,4表示可读权限7:可读可写可执行 4+2+16:可读可写 4+25:可读可执行4+1 $ chmod 721 a1.index.sh # 421模式修改与之类似的还有chown与chgrp,这两个权限更大,需要root权限;chown: Change owner$ chown 用户名 目录名/ # 修改目录的属主chgrp: Change group$ chgrp 组名 目录名/ # 修改目录的组名 exit退出登录,exit是正确退出,最好不要直接点windows关闭窗口按钮退出,也不要使用ctrl+D给定退出信号退出。 man详细解释命令,系统命令可以用这个找,下载的程序往往是–help wget后面接下载网址,可以直接由地址获取下载文件 su:super user获得超级管理员权限,root权限,需要输入密码sudo:super user do暂时取得root权限,配置系统经常能看到sudo yum echo在标准输出(屏幕)上显示文字 12-n 输出之后不换行,去除结尾的换行符。注意默认一行后有一个换行符-e 转义字符按照对应方式处理 yum(centos是yum,ubuntu是apt) Yellow dog Updater Modified是一个软件包管理器,能够从指定的服务器自动下载rpm包进行安装并且自动处理依赖性关系,yum优点提供了查找、安装、删除某一个、一组甚至全部软件包的命令,并且命令简洁便于使用。 1234567891011121314151617181920yum clean all # 清除原有yum缓存yum repolist # 列出仓库信息yum install software # 安装yum update # 更新yum list software # 查看软件yum list all # 查看所有软件yum list installed # 列出已安装软件yum list available # 列出可安装软件yum reinstall software # 重新安装yum remove software # 卸载yum info software # 查看软件信息yum search software # 根据软件信息查找软件yum whatprovides file # 根据文件找出包含此文件的软件yum history # 查看系统中软件管理信息yum history info 数字 # 对该数字为id的信息进行显示yum groups list # 列出软件组 yum groups info # 查看软件组的信息yum groups install sfgroup # 安装软甲组yum groups remove sfgroup # 卸载软件组yum repolist # 查看yum源信息 cut命令从文件的每一行剪切字节、字符和字段并将这些字节、字符和字段写至标准输出如果不指定 File 参数,cut 命令将读取标准输入。必须指定 -b、-c 或 -f 标志之一 1234-b 以字节为单位进行分割。这些字节位置将忽略多字节字符边界,除非也指定了 -n 标志-c 以字符为单位进行分割-d 自定义分隔符,默认为制表符-f 与-d一起使用,指定显示哪个区域 xargs与管道不同,xargs可以给下个命令传递参数。$ ls *.gz | head #只可以输出前10个文件名$ ls *.gz | xargs head #输出.gz结尾的所有文件前10行这里要注意下其实命令是有省略的,完整应该是ls *.gz | xargs -i head{} #传递参数到head的花括号中 jobs查看当前在后台执行的命令,可查看命令进程号码 &运行命令时,在命令末尾加上&可让命令在后台执行 顺便说一下 | ; && ||区别 1234&& 左边命令成功运行了,右边命令才会运行,就是逻辑与的功能; 不管左边命令有没有成功运行,右边命令都会运行,两者之间独立| 左边命令的结果作为右边命令的参数,注意与xargs区分|| 左边运行的命令失败,右边的命令才会运行,否则只显示左边命令运行结果 nohup命令可以使命令永久的执行下去,和终端没有关系,退出终端也不会影响程序的运行; & 是后台运行的意思,但当用户退出的时候,命令自动也跟着退出。 那么,把两个结合起来nohup 命令 &这样就能使命令永久的在后台执行 fg N将命令进程号码为N的命令进程放到前台执行,同%N #注意是进程号不是PID!kill程序需要PID bg N将命令进程号码为N的命令进程放到后台执行 cal 显示日历 date 显示时间 2. 基本操作源码编译安装软件都有Readme文件或者install文件说明安装方式,一般是以下步骤:1、运行configue脚本 #检查系统环境配置情况,缺少哪些东西,缺少的可以yum下载安装2、运行make check命令(可选)3、敲make命令进行编译4、make install命令安装,出现可执行程序 文件校验下载大的文件会附带.md5文件任意长度信息逐位计算,产生128位hash值,不可逆。也就是说MD5算法可以位任何文件产生一个独一无二的数据指纹,通过校验下载前后的MD5值是否发生改变,就可以知道源文件是否被改动$ md5sum filename > data.md5 # 对文件(可多个文件)生成md5校验码(32位,16进制),并命名为data.md5$ md5sum -c data.md5 # 校验文件,如果校验码相同则显示OK 重定向本质是将输出到屏幕的内容重定向到一个新的文件夹中,大于号和小于号都是代表数据的流向$ echo “想要的内容”> 文件名 #覆盖原文件的内容$ echo “想要的内容”>> 文件名 #想要的内容追加到文件后,原文件内容不修改一个>是覆盖,两个>>是追加 Ctrl+C终止并退出前台命令的执行,回到SHELL Ctrl+Z暂停前台命令的执行,将该进程放入后台,回到SHELL 3. vimvim(主要用来写脚本,编辑文件)vim是Linux系统自带的文本编辑器,可以理解成为windows系统下的word软件,适合编辑小文件,会一次加载全部内容到内存 123:w filename 将文件以指定的文件名保存起来 :wq 保存并退出:q! 不保存而强制退出 注意vim是vi的拓展,有些自定义设置要在vim下生效,最好是用vim用户设置优先级高于全局设置,设置文件都在家目录~下设置,且均为点开头的隐藏文件,如下~/.vimrc~/.bashrc 3.1 命令行模式功能键:1)插入模式 123i 切换进入插入模式 insert mode ,按"i"进入插入模式后是从光标当前位置开始输入文件a 进入插入模式后,是从目前光标所在位置的下一个位置开始输入文字o 进入插入模式后,是插入新的一行,从行首开始输入文字 2)从插入模式切换为命令行模式按 ESC 键3)移动光标直接用键盘上的光标来上下左右移动,也可以用小写英文字母h、j、k、l,分别控制光标左、下、上、右移一格。 1234567G 移动到文件末尾,15G移动光标至文章的第15行行首gg 移动到文件开头$ 移动到光标所在行的行尾^ 移动到光标所在行的行首H 光标移动到这个屏幕的最上方那一行的第一个字符M 光标移动到这个屏幕的中央那一行的第一个字符L 光标移动到这个屏幕的最下方那一行的第一个字符 4)删除文字 123x 每按一次,删除光标所在位置的后面一个字符X 大写的X,每按一次,删除光标所在位置的前面一个字符dd 删除光标所在行 1,6d删除1到6行 5)回复上一次操作 1u 如果误执行一个命令,可以回到上一个操作。按多次u可以执行多次回复 6)继续下一个操作 12n或. 比如查找一个字符串以后,继续寻找下一个字符串,按多次n执行多次操作N 与 n 刚好相反,为反向进行前一个搜寻动作 3.2 底线命令模式1234567:/word # 查找word字符串:%s/x/y/gc # 所有x被y替换 g代表全局,c代表交互模式(每次替代会提示):!命令 # 命令先执行,vim被挂起。执行后按enter回到vim:split # 横屏分屏显示 ctrl+ww切换上下屏:vsplit # 纵向分屏:only # 取消分屏:n1,n2s/word1/word2/g # 在第n1与n2行之间寻找word1这个字符串,并将该字符串取代为word2 vim还有专门的键盘图。。。放一个简略版的 4. 基础命令三剑客三剑客的命令非常之多,完全可以出一本书,这里只放一些简单的和我能用得到的 4.1 三剑客之grepgrep(找基因信息比较方便)Global Regular Expression Print,全局正则表达式版本文本搜索工具,类似于正则表达式搜索,可以在一个大的文件中快速搜索到满足一定规则的内容。 $ grep ">" gene.fna | wc -l # 统计gene.fna文件中序列的条数$ grep -A 2 "3 gi 29732 34486" lastz.axt #将满足条件的行和下面两行显示出来 12grep -E # grep的拓展模式grep -P # 适应perl语言的正则表达式 区分一下:find是搜索目录下满足条件的文件,grep是搜索文件内满足条件的内容 4.2 三剑客之sedsedsed = Stream Editor流处理器,数据流过这个工具,格式化成固定的格式sed + 选项参数 + '模式' + 文本或文件 选项参数: 12345-e 替换,并输出到屏幕(搭配重定向)-i 原文件修改-f 根据模式替换-r 拓展的正则表达式-n 输出 模式: 1234g 全局s 替换,一个字符替换另一个d 删除p 打印 输出固定的行$ sed -n '1307p' seq.fna # 输出文件第1307行;$ sed -n '100,200p' seq.fna # 输出文件第100到200行; 替换操作$ sed -e 's/gi/GI/' seq.fna # 将文件中gi全部替换为大写GI;s为替换$ sed -i 's/gi/GI/g' seq.fna # 在原文件上进行替换,并且进行全部替换,g为全局(默认只进行一次替换) 删除操作$ sed -e '/^\\s$/d' seq.fna # 删除文件中的空白行,命令d为删除符合条件的行。\\s为空白;^行首,$行尾$ sed -e '/>/d' seq.fna # 删除包含ref的行,每个ref行都有>$ sed -e 's/:.*//g' seq.fna # 删除冒号之后的所有内容 4.3 三剑客之awkawk也是非常强大的文本处理工具,awk本身也是一门编程语言输出一个列表任意列$ awk '{print $1,$NF}' 1.txt # 输出1.txt的第一列和最后一列 过滤文件结果$ awk '{if ($3>=80 && $4>=100) print $0}' blast_m8.out # 过滤文件比对结果,将第三列值大于80,并且第四列值大于100的所有结果输出 比较$ awk '$8>$10' input.txt # 输出第8列数值大于第10列数值的行 输出固定行内容$ awk 'NR>=20&&NR<=80' input.txt #输出第20到第80行内容 5. 正则表达式正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑 123456789101112131415161718^ 匹配输入字行首 ^eat,识别eat开头的字符串$ 匹配输入行尾 eat$,识别eat结尾的字符串 \\b 单词锚定符 \\beat\\b ,只识别eat字符串. 匹配除“\\n”和"\\r"之外的任何单个字符\\ 转译字符 比如匹配. 则\\.* 匹配前面的子表达式任意次+ 匹配前面的子表达式一次或多次(大于等于1次,例如“zo+”能匹配“zo”以及“zoo”,但不能匹配“z”)# 需要grep -E支持(拓展)? 匹配前面的子表达式零次或一次 # 需要grep -E支持(拓展)[xyz] 字符集合。匹配所包含的任意一个字符x|y 匹配x或y。“z|food”能匹配“z”或“food”。“[z|f]ood”则匹配“zood”或“food”,择译匹配[a-z] 字符范围\\d 匹配所有数字,等同[0-9]\\s 空白,是字符集换页、制表、换行、回车以及空格的简写[\\f\\t\\n\\r]\\w [A-Za-z0-9_]单词包括大小写字母、数字和下划线^ 负值字符范围。匹配任何不在指定范围内的任意字符。(倒三角)\\D 非数字\\W 非字符\\S 非空白字符","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"linux指令","slug":"linux指令","permalink":"http://www.shelven.com/tags/linux%E6%8C%87%E4%BB%A4/"}]},{"title":"shell脚本基本语法总结","slug":"shell脚本基本语法总结","date":"2022-04-19T19:55:58.000Z","updated":"2022-12-03T16:17:05.000Z","comments":true,"path":"2022/04/20/a.html","link":"","permalink":"http://www.shelven.com/2022/04/20/a.html","excerpt":"简单记录下shell脚本语言的学习。","text":"简单记录下shell脚本语言的学习。 shell脚本运行方式首先要了解什么是脚本,脚本本质上是一个可运行的文件,使用特定格式的指令让系统通过脚本解析器解析并执行你的指令。系统提供的shell命令解析器有sh、bash和ash。可以通过echo $SHELL查看自己linux系统的默认解析方式 shell脚本文件的开头:#!/bin/bash #! 是特殊的用来声明脚本由什么shell解释,否则使用默认shell sh文件有三种执行方式./xxx.sh bash xxx.sh . xxx.sh ./xxx.sh 先按照 文件中#!指定的解析器解析,如果#!指定指定的解析器不存在才会使用系统默认的解析器 bash xxx.sh 指明先用bash解析器解析,如果bash不存在才会使用默认解析器 . xxx.sh 直接使用默认解析器解析 各种引号的区别vim创建脚本文件1111.sh: 123456789#!/bin/bashecho "Phantom的SHELL练习"num=123echo "预设数字=$num"read -p "输入数字" sum # read可以识别标准输入(键盘输入),-p参数设置提示语echo "输出结果=$sum+$num"echo "$sum" # ""解析变量值echo '$sum' # ''不能解析变量值echo "今天日期`date`" # ``识别为系统命令 变量名不能以数字开头 在变量赋值的过程中,等号两边不能接空格,若要接空格,则整个字符串都要用引号括起来 各种引号区别双引号“”可以解析变量的值单引号‘’不能解析变量的值,包含的变量会被当做字符串反引号`` 反引号的内容作为系统命令并执行 如`date` 各种括号的区别vim创建脚本文件xxx.sh: 1234567#!/bin/bashNum=1000{ # 花括号表示在当前shell完成,会影响当前变量 Num=1234 echo "()里面的数字是=$Num"}echo "显示当前shell数字=$Num" vim创建脚本文件xxxx.sh: 1234567#!/bin/bashNum=1000( # 小括号表示在当前shell完成,不会影响当前变量 Num=1234 echo "()里面的数字是=$Num")echo "显示当前shell数字=$Num" {命令序列} 在当前shell中执行,直接影响当前变量(命令序列) 由子shell完成,不影响当前shell的变量[判断条件]中括号是判断条件,进行数值判断。下面会说明 数值判断vim建立脚本文件xxxxx.sh: 12345678910#!/bin/bashread -p "请输入第一个数字" mread -p "请输入第二个数字" nif [ $m -eq $n ]; then # -eq 判断两个参数是否相等 echo "输入的两个数字相等"elif [ $m -lt $n ]; then # -lt 判断左边参数是否小于右边参数 echo "第一个数字小于第二个数字"elif [ $m -gt $n ]; then # -gt 判断左边参数是否大于右边参数 echo "第一个数字大于第二个数字"fi # if控制语句格式:if elif else fi 数值判断参数详解-eq 比较两个参数是否相等-ne 比较两个参数是否不相等-lt 左边参数是否小于右边参数-le 左边参数是否小于等于右边参数-gt 左边参数是否大于右边参数-ge 左边参数是否大于等于右边参数 字符串提取和替换vim新建脚本文件1234.sh: 1234567#!/bin/bashll="Phantom Aria f r u i t l e s s l o v e" # 定义字符串变量echo "长度为:${#ll}" # 字符串长度(包括空格)echo "${ll:3}" # 从第3个字符往后提取echo "${ll:3:11}" # 从第3个字符往后提取11个字符echo "${ll/ /}" # 字符串从左往右删除第一个空格(相当于替换的方式)echo "${ll// /}" # 删除字符串中所有空格(相当于全局替换的方式) 字符串匹配和删除vim新建脚本文件match.sh: 123456#!/bin/bashll="Phantom Aria fruitless love"echo ${ll% *} # 从右往左匹配第一个空格,删除右边所有字符串echo ${ll%% *} # 从右往左匹配所有空格,删除右边所有字符串echo ${ll#* } # 从左往右匹配第一个空格,删除左边所有字符串echo ${ll##* } # 从左往右匹配所有空格,删除左边所有字符串 *号是通配符,可以是匹配的任意长度任意字符串%和%%匹配原则:都是从右到左匹配,删除右边,%%称为贪婪匹配#和##匹配原则:都是从左往右匹配,删除左边,##同样称为贪婪匹配,注意通配符位置 for循环语句for循环语句两种写法如下: 123456789for ((初始值;限制值;执行步阶)) #注意两个小括号,少一个都不行do 程序段done或for 变量 in 1 2 3 4 5 6 7 8 9 10 #等价于`seq 1 10`do 程序段done vim建立脚本文件for_example.sh: 12345678#!/bin/bashdeclare -i sum=0 # 强制定义sum为整数型变量(不定义会变成一串字符串)read -p "请输入整数" n # 标准输入定义变量nfor (( i=0; i<=$n; i++ )) # 等同于for i in `seq 0 $n`,不赘述do sum=$sum+$i # 计算0到n之和doneecho "0到这个数的整数之和=$sum" 自定义函数vim建立脚本文件12345.sh: 12345678910111213#!/bin/bashfunction formax(){ if [ $n -gt $m ]; then return $n else return $m fi}read -p "输入数值1:" nread -p "输入数值2:" mformax $n $mecho "输入的最大值为$?" # $?表示上个指令的返回值 自定义了一个formax函数判断输入的两个数值大小,可以看出shell脚本中是一行一行读取指令的。自定义函数可以被引用,保存上述{}内的指令至原文件名12345.sh,在下一个脚本文件中,将函数放在脚本开始处, shell解释器发现它才可以进行调用(如下所示) vim建立脚本文件test.sh: 123456#!/bin/bashsource 12345.shread -p "输入数值1:" nread -p "输入数值2:" mformax $n $mecho "输入的最大值为$?" 自定义函数被成功调用","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"shell脚本","slug":"shell脚本","permalink":"http://www.shelven.com/tags/shell%E8%84%9A%E6%9C%AC/"}]},{"title":"转录组数据分析笔记(7)——DESeq2差异分析","slug":"转录组数据分析笔记(7)——R包DESeq2基因差异表达分析","date":"2022-04-18T07:49:20.000Z","updated":"2022-12-03T16:33:19.000Z","comments":true,"path":"2022/04/18/a.html","link":"","permalink":"http://www.shelven.com/2022/04/18/a.html","excerpt":"前面说到DESeq2包需要准备两个输入文件,一个是样本列表矩阵,一个是row count定量表达矩阵,接下来我们要对样本进行两两比对,找到两组之间有多少个基因上调和下调,不进行两两比对直接把4组数据4个重复全部导进去得到的结果是没有意义的,这里用DESeq2做表达基因的差异分析","text":"前面说到DESeq2包需要准备两个输入文件,一个是样本列表矩阵,一个是row count定量表达矩阵,接下来我们要对样本进行两两比对,找到两组之间有多少个基因上调和下调,不进行两两比对直接把4组数据4个重复全部导进去得到的结果是没有意义的,这里用DESeq2做表达基因的差异分析 举个栗子,我先进行短日照的“0”组样本和经过“1”天长日照的1组样本之间进行差异基因分析,从前面整理的样本列表矩阵我们可以看到,0组样本四个重复分别是ERR1698194、ERR1698202、ERR1698203和ERR1698204,同样1组数据4个重复分别为ERR1698205、ERR1698206、ERR1698207和ERR1698208,因此我们需要重新整理我们需要的数据做成csv格式,如下所示: 定量表达矩阵第一行需要和样本列表矩阵的第一列一一对应,顺序需要一模一样下面讲解如何使用DESeq2 1. 代码示范前面处理好raw count定量表达矩阵,建立样本列表矩阵后,我们就可以在rstudio里运行DESeq2包进行差异基因筛选了。代码如下。 1234567891011121314151617181920212223library("DESeq2")mycounts <- read.csv("gene_count_matrix_0_1.csv",row.names = 1)mycounts_1 <- mycounts[rowSums(mycounts) != 0,] # 重新定义数据集,过滤mapping数为0的基因mymeta <- read.csv("sample_list_0_1.csv",stringsAsFactors = T) # 载入样本分组文件,遇到字符串将其转化为因子colnames(mycounts_1) == mymeta$id # 检查导入的两个数据集是否匹配,返回值为F需要重新匹配mymeta$index <- factor(mymeta$index,levels = c("0","1")) # 把样本分组文件的分组列转换到因子,两两比对把对照组放前面!dds <- DESeqDataSetFromMatrix(countData = mycounts_1, colData = mymeta, design = ~index) #构造用于差异表达分析的数据集dds <- DESeq(dds)res <- results(dds)res_1 <- data.frame(res) # 结果res不是常规的数据,需要转化成数据框library("dplyr")res_1 %>% # dplyr给数据集增加新列 mutate(group = case_when( log2FoldChange >=1 & padj <=0.05 ~ "UP", log2FoldChange <=-1 & padj <=0.05 ~ "DOWN", TRUE ~ "NOT_CHANGE" )) -> res_2table(res_2$group)write.csv(res_2,file = "gene_0_1.csv", quote = F) # 输出文件 2. 代码详解详细解释一下过程: 在R里运行程序或者写代码,首先要确定好工作目录在哪里,将之前Stringtie转化的定量表达矩阵和样本列表矩阵全都放在工作目录下,这里我的表达量矩阵是transcript_count_matrix_0_1.csv,分组列表矩阵是sample_list_0_1.csv。getwd()可以查看当前工作目录,在全局设置里可以更改工作目录。 library("DESeq2") # 加载DESeq2这个R包 mycounts <- read.csv("gene_count_matrix_0_1.csv",row.names = 1) # 载入raw count矩阵,以第一列数据作为行名,读取的矩阵命名为mycounts mycounts_1 <- mycounts[rowSums(mycounts) != 0,] # 过滤每一行mapping总数为0的基因,将数据集整理命名为mycounts_1 mymeta <- read.csv("sample_list_0_1.csv",stringsAsFactors = T) # 载入样品列表,遇到字符串将其转化为一个因子 colnames(mycounts_1) == mymeta$id # 检查raw count矩阵第一行是否与样品列表的id列是否一致(如下)。这个很重要,不一致跑DESeq2会报错。如果显示false就要调整 mymeta$index <- factor(mymeta$index,levels = c("0","1")) # 这一步同样重要,把样本分组文件的分组列转换到因子,不然会报错。我这里对照组是第0天,所以把“1”放在“0”之后,这里顺序需要特别说明!样本和定量矩阵的分组只要一一对应可以不排序,这里一定要分清楚哪个组和哪个组进行比较,否则会得出完全相反的结论! 123dds <- DESeqDataSetFromMatrix(countData = mycounts_1, colData = mymeta, design = ~index) # 中间那一长串是DESeq2包里的函数,countData是raw count定量矩阵,colData是样品列表,design是分组信息,这步是为了构造用于差异表达分析的数据集,并将数据集命名为dds dds <- DESeq(dds) # 分析的核心DESeq程序 res <- results(dds) # 将结果输出至res数据集 res_1 <- data.frame(res) # res不是常规的数据,我们可以用head和class命令查看一下(如下图),需要转化成常规的数据框格式才可以对其进行加减列等操作,转换格式后的数据集名字为res_1 library("dplyr") # 加载这个包是为了对数据框进行操作,我是要增加新的一列统计差异表达情况 123456res_1 %>% mutate(group = case_when( log2FoldChange >=1 & padj <=0.05 ~ "UP", log2FoldChange <=-1 & padj <=0.05 ~ "DOWN", TRUE ~ "NOT_CHANGE" )) -> res_2 # 调用dplyr包给数据集增加新的一列group,log2FoldChange >=1,padj <=0.05,判断这个基因表达为上调,在log2FoldChange <=-1,padj <=0.05时判断这个基因表达为下调,其余情况为该基因表达情况不变。将结果输出到res_2数据集。 FoldChange表示两样品间表达量比值,是倍数变化,差异表达基因分析里,log2 fold change绝对值大于1为差异基因筛选标准。padj是调整后的p值,在p检验里,p值小于0.05是有显著差异的标志。 table(res_2$group) # 查看差异基因表达的结果,上调基因多少,下调基因有多少,不变的有多少 write.csv(res_2,file = "gene_0_1.csv", quote = F) # 输出和生成gene_0_1.csv文件,即为结果文件 3. 结果演示我对“0”组样本(对照)和长日照1天,2天和3天这一共4组样本分别进行两两对比,做了基因表达差异分析(0_1表示0组和1组对比,1_2表示1组和2组对比,依次类推不再赘述),同时与原文献补充数据2中的差异分析结果做对比 0组与1组对比,187个基因上调,149个基因下调;原文57个基因上调,79个基因下调 0组与2组对比,51个基因上调,42个基因下调;原文21个基因上调,24个基因下调 0组与3组对比,142个基因上调,143个基因下调;原文107个基因上调,84个基因下调 1组和2组对比,26个基因上调,29个基因下调;原文3个基因上调,0个基因下调 1组和3组对比,46个基因上调,50个基因下调;原文8个基因上调,2个基因下调 2组和3组对比,11个基因上调,13个基因下调;原文2个基因上调,1个基因下调 点击这里下载原文基因表达差异总表(补充数据2) 3.1 分析我做的差异分析总体趋势和文章类似,都是短日照组(SD组,也就是0组)与长日照1、2、3天组(LD组,分别对应123组)相比,有较多基因出现差异性表达;而长日照1、2、3天组之间相比差异表达基因较少,这样才合理,表明拟南芥茎尖分生组织在开花期长日照下,确实有不同的基因参与了光周期诱导的开花过程。如果要对这些差异表达的基因做更深入的分析(下游分析),我们就要比对GO库或者KEGG库进行代谢通路富集分析和注释。以后的笔记会说到。至于为什么我得到的差异基因普遍比原文多,一个是差异分析之前用的分析软件不同,在过滤数据过程中我的条件比较松(只过滤了count数为0的基因),另外不同软件的组装、比对和计数的算法实现也不一样。另外,我们得到这个基因差异表达结果之后,还可以做个更直观的火山图看我们的差异基因分布是否合理,之后也会说。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"转录组数据分析","slug":"转录组数据分析","permalink":"http://www.shelven.com/categories/%E8%BD%AC%E5%BD%95%E7%BB%84%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"}],"tags":[{"name":"DESeq2","slug":"DESeq2","permalink":"http://www.shelven.com/tags/DESeq2/"},{"name":"dplyr","slug":"dplyr","permalink":"http://www.shelven.com/tags/dplyr/"},{"name":"R语言","slug":"R语言","permalink":"http://www.shelven.com/tags/R%E8%AF%AD%E8%A8%80/"}]},{"title":"转录组数据分析笔记(6)——HTseq计数定量","slug":"转录组数据分析笔记(6)——HTseq计数定量","date":"2022-04-17T15:49:37.000Z","updated":"2022-12-03T16:33:39.000Z","comments":true,"path":"2022/04/17/b.html","link":"","permalink":"http://www.shelven.com/2022/04/17/b.html","excerpt":"HTseq也是对有参考基因组转录数据进行表达量分析的,主要用于reads计数。这个软件功能就比较专一,不像stringtie还需要运行prepDE.py脚本进行数据转化,直接一步到位。那为什么我一开始不用HTseq呢?因为我遇到一个bug 主要还是运算速度的问题,我比较了两种定量方式,HTseq定量虽然只有一步,但是速度远不如stringtie,也可能是我的问题,下面会说到。","text":"HTseq也是对有参考基因组转录数据进行表达量分析的,主要用于reads计数。这个软件功能就比较专一,不像stringtie还需要运行prepDE.py脚本进行数据转化,直接一步到位。那为什么我一开始不用HTseq呢?因为我遇到一个bug 主要还是运算速度的问题,我比较了两种定量方式,HTseq定量虽然只有一步,但是速度远不如stringtie,也可能是我的问题,下面会说到。 1. HTseq定量获得raw countvim一个新脚本,输入如下命令: 12345678#!/bin/bashfor i in `seq 194 209`do htseq-count -f bam -s no \\ /media/sf_/data/fastq/bam/ERR1698"$i".bam \\ # 输入bam文件 /media/sf_/data/ref/Arabidopis_thaliana.gtf # 参考基因组注释文件 > /media/sf_/data/fastq/count/ERR1698"$i".count # 自定义输出文件done 参数详解-f # 设置输入文件格式,可以是bam或者sam-s # 设置是否是链特异性测序,设置no每一条reads都会和正义链和反义链进行比较 保存运行以后发现这个程序只能分配一个线程(也可能是我没找到分线程的方法),所以可以根据电脑内核数分几个批处理一起运行会快很多(不然就等着干瞪眼= =)。 还有一点非常重要!bam文件需要提前按照名称排序,不然会出现绝大部分reads mapping不到参考基因组,这种情况会在屏幕上输出提示信息,但是程序还是会继续跑……这时候就别犹豫了赶紧kill这个程序,就算跑完了数据都不能用。可以用samtools sort -n对bam文件进行名称排序,但是排序之后无法再用samtools index建立索引文件,这会导致HTseq运行速度比蜗牛还慢。暂时没找到更好的办法 摊手。 经过漫长长长长长的时间等待,我们可以看看结果文件的head和tail(这里就放一张图吧): 前面记录了基因名称和mapping上的reads数,最后5行对应不同的mapping情况,在不同的模式下意义不同,官网给出的区别如下图,默认是union模式: 计数结果也可以用multiqc合并,生成在线报告,这里可以直观地看到每个样品比对上的reads数百分比,这里16个样品的比对率都超过80%,说明计数结果都还不错。 2. HTseq结果文件处理HTseq计数定量后得到的是每一个样品的每个基因reads数,我们需要合并每个样品定量数据,手动修改成DESeq2能识别的raw count表达矩阵,还需要再准备一个样本列表矩阵,才能进行后续的DESeq分析。参考一下stringtie最后生成的表达量矩阵文件,我们也需要将HTseq定量结果整理成csv格式(逗号作为分隔符),第一列是基因名,后面是按照样品序列的排序,中间是表达矩阵。 再来看一看HTseq定量生成的文件详情,同样第一列是基因名,后面是raw count数量,^I 表示两列数据是以制表符tab键分隔的,$为换行符。 我的方法比较笨比,除第一个ERR1698194.count文件保留外,其他所有count文件第一列删去并命名为cut.count,然后合并ERR1698194.count和其他所有cut.count文件,再将所有的制表符替换为逗号,最后加上第一行行名和改文件名。 用awk命令删除第一列,写入到新的cut.count文件中: 1234for i in `seq 195 209`do cat ERR1698"$i".count | awk '{$1 = ""; print $0}' > ERR1698"$i"cut.countdone paste组合ERR1698194样品和其他cut.count文件到alldata.count: $ paste ERR1698194.count *cut.count > alldata.count 看看alldata.count的数据格式,列数没有问题,但是awk删除列产生了空格: 用sed命令删除所有空格,替换所有制表符为逗号(两步可以合一步): $ sed 's/ //g' alldata.count > alldata1.count $ sed 's/\\t/,/g' alldata1.count > alldata2.count 这样就手动生成符合csv格式的文件了,只需加上第一行: 这里样本量比较少,我直接vim复制粘贴的方法加了第一行,重命名一下文件就完成了表达矩阵的制作,可以用于DESeq2分析了! 因为本人比较小白,上面处理过程就有些啰嗦了,总的思路就是改成csv格式文件的样式就可以。 样本列表矩阵的制作过程和stringtie一模一样,点击这里查看,本篇不再赘述。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"转录组数据分析","slug":"转录组数据分析","permalink":"http://www.shelven.com/categories/%E8%BD%AC%E5%BD%95%E7%BB%84%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"}],"tags":[{"name":"HTseq","slug":"HTseq","permalink":"http://www.shelven.com/tags/HTseq/"}]},{"title":"转录组数据分析笔记(5)——stringtie转录本组装和定量","slug":"转录组数据分析笔记(5)——stringtie转录本组装和定量","date":"2022-04-17T15:14:21.000Z","updated":"2022-12-03T16:35:23.000Z","comments":true,"path":"2022/04/17/a.html","link":"","permalink":"http://www.shelven.com/2022/04/17/a.html","excerpt":"本篇笔记主要记录如何用Stringtie做转录本的组装和定量,以及如何制作样本列表矩阵,为后面DESeq2分析做铺垫。","text":"本篇笔记主要记录如何用Stringtie做转录本的组装和定量,以及如何制作样本列表矩阵,为后面DESeq2分析做铺垫。 stringtie转录本组装和定量1 转录本组装Stringtie是一个基因和转录本组装定量的软件,stringtie的输入文件有两个,一个是经过排序的bam文件,排序可以用前面说到的samtools sort命令完成,还有一个是参考基因组的注释文件(gff或gtf格式)。 在使用Stringtie进行基因或者转录本组装定量的过程中,有一个非常重要的参数 - e,我之前跑了一遍流程没有加参数-e,结果组装的结果非常差,还有大量的未注释的基因。我请教了度娘,网上的教程攻略也都是抄来抄去的没解决什么问题,官网只有这么一句解释: -e this option directs StringTie to operate in expression estimation mode; this limits the processing of read alignments to estimating the coverage of the transcripts given with the -G option (hence this option requires -G). 对于加了参数-e之后如何做的比对和组装处理还是不明了,不知道表达评估模式的原理是什么,只能自己做个大概的总结(不知正确与否): 如果我们研究的样本没有很好的注释信息,研究的人少,现有的注释信息都不完善,那么我们就需要重建转录本进行注释,这个时候就不需要加参数-e。如果样品的注释信息非常完整,比如拟南芥这种模式生物,我们不需要重建新的转录本进行注释,只对现有的参考基因组注释文件就足够了,那就要用-e参数,不需要预测新的转录本。 -e参数还有个比较重要的地方,只有用了-e参数后,才可以运行prepDE.py3脚本得到read count矩阵(也就是进行定量),这个脚本后面会说。 我们首先创建一个shell脚本进行转录本组装: 12345678#!/bin/bashfor i in `seq 194 209`do stringtie -p 4 -e \\ -G /media/sf_/data/ref/Arabidopis_thaliana.gtf \\ # 参考基因组注释文件 -o /media/sf_/data/fastq/gtf/ERR1698"$i".gtf \\ # 自定义输出文件 /media/sf_/data/fastq/bam/ERR1698"$i".bam # 输入的bam文件done 保存,运行,我们可以得到.gtf格式文件,less一下查看里面的内容: 我们这里因为加了参数-e,不会有新的基因和转录本,可以看到每个read比对上的基因的信息。(不加参数-e会组装新基因和转录本,默认采用STRG加数字编号进行区分)。每行数据会给出coverage,FPKM和TPM三个信息,后两者都可以用来定量。FPKM和TPM都是对read counts数目进行的标准化,如果是单端测序数据可以用RPKM进行标准化,不进行数据标准化的比较是没有意义的。 2 合并转录本(重构转录本才需要)这一步要注意下,如果需要重构转录本才需要合并所有的转录本的组装结果,得到一个非冗余的转录本合集,也就是获得跨多个RNA-seq样品的全局的转录本。这里需要分两步: $ ls *.gtf > mergelist.txt # 将所有组装的转录本文件名合并到一个文件 $ stringtie --merge -p 4 -G /media/sf_/data/ref/Arabidopis_thaliana.gtf -o merge.gtf ./mergelist.txt #这一步是用--merge指令将所有转录本合并输出到merge.gtf文件中 我们最后得到的merge.gtf就是全局的转录本。这里只是记录一下这步操作,我们只关注参考基因组的注释结果就不需要merge。 3 获得定量表达矩阵DESeq2要求输入的定量结果为raw count形式,raw count是根据mapping到基因或转录本的reads数计算得到,而stringtie只提供了转录本水平的表达量,定量方式包括TPM和FPKM值两种。为了进行raw count定量,stringtie官方提供了prepDE.py脚本(两个版本,我选择的python 3版本,在我base环境下不会冲突),可以计算出raw count的表达量。 下载这个python脚本,如果你用的是windows浏览器,在官网找到脚本直接右键复制链接,用wget直接下到linux系统里,千万不要在windows上直接复制粘贴代码过去。因为windows的换行符和linux的不一样,两个系统间直接粘贴代码会出现错行和莫名其妙的缩进导致程序报错(可以用cat -A看两个系统换行符的区别,血的教训,排查了老半天才发现)推荐用prepDE.py3,不用再切python 2 的环境了。 官方给出的prepDE.py脚本有两种运行方式(如下图所示),一种是建立Ballgown能识别的目录结构,一种是建立sample_lst文件并指定所有样品数据的路径。两种方法都可行,Ballgown现在用的比较少,比较主流的还是Stringtie+DESeq2的分析方法。演示一下如何创建sample_lst和解释一下这个文件要求的格式。 3.1 sample_lst文件准备简单来说,sample_lst.txt要求第一列为样品编号,第二列为对应编号的样品gtf文件所在路径,中间用制表符tab隔开,如下图(命名不一定要完全一样,注意格式,后面要导入prepDE脚本,能找到就行): 这个文件准备工作比较简单,不再赘述 3.2 运行prepDE.py3将prepDE.py3脚本放在上面gtf文件的目录下,运行以下命令: $ python prepDE.py3 -i sample_lst.txt -g gene_count_matrix.csv -t transcript_count_matrix.csv 解释一下: 参数含义-i # 输入文件,就是前面做的sample_lst.txt-g # 自定义基因组表达矩阵名字,默认也是gene_count_matrix.csv-t # 自定义转录本表达矩阵名字,默认也是transcript_count_matrix.csv 得到的这两个文件就是基因和转录水平的raw count表达量矩阵,我们都可以用于后面的DESeq2分析。 4. 制作样本列表矩阵这里需要和前面为了运行prepDE.py脚本而制作的sample_lst文件区分开,要做下一步DESeq2差异基因分析,我们需要自己手动创建一个DESeq2能识别的样本列表矩阵,包含两列信息:一列是样本名称,一列是样本分组。样本分组信息我们可以直接从下载样本数据的地方(EBI官网)得到,只需要自己改一下格式。 下载之后发现第一行标题特别长,稍微处理下制表符替换成换行符,将第一行标题拆分成每个字段一行的格式,找一下不同天数处理的分组信息关键字“time”,发现我们要的分组信息在第36行(也就是原来文件的第36列): $ head -n1 E-MTAB-5130.sdrf.txt | tr '\\t' '\\n' | nl | grep "time" 同样的方法找样本信息所在列是32列: $ head -n1 E-MTAB-5130.sdrf.txt | tr '\\t' '\\n' | nl | grep "ENA" 所以我们需要提取第32列和第36列,用cut命令切割并重定向到新的文件sample_list: $ cut -f 32,36 E-MTAB-5130.sdrf.txt > sample_list.csv 发现相邻数据有重复,uniq删除重复行,再用sed替换制表符为逗号(因为csv文件就是以逗号作为分隔符),将原来的sample_list.csv覆盖,vim手动修改一下第一行名字,完成后就可以用于DESeq2分析了! $ uniq sample_list.csv > sample_list1.csv # uniq删除重复行 $ sed 's/\\t/,/g' sample_list1.csv > sample_list.csv # 替换制表符为逗号 手动修改sample_list .csv第一行内容,修改之后如下即可 更新2022/4/22:这里的处理仅仅是做到符合DESeq2输入的格式,在进行两组样本基因表达差异分析的时候,还需要分别建立两两比对的样本列表矩阵和定量矩阵,不然差异分析没有意义,详见DESeq2笔记","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"转录组数据分析","slug":"转录组数据分析","permalink":"http://www.shelven.com/categories/%E8%BD%AC%E5%BD%95%E7%BB%84%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"}],"tags":[{"name":"stringtie","slug":"stringtie","permalink":"http://www.shelven.com/tags/stringtie/"},{"name":"prepDE.py3","slug":"prepDE-py3","permalink":"http://www.shelven.com/tags/prepDE-py3/"}]},{"title":"转录组数据分析笔记(4)——IGV基因组浏览器安装和解读","slug":"转录组数据分析笔记(4)——IGV基因组浏览器安装和解读","date":"2022-04-16T11:32:50.000Z","updated":"2022-12-03T16:36:31.000Z","comments":true,"path":"2022/04/16/b.html","link":"","permalink":"http://www.shelven.com/2022/04/16/b.html","excerpt":"IGV(Integrative Genomics Viewer)是一个非常方便的比对软件,在使用前只需要将参考基因组和bam文件分别建立索引(即建立fai和bai文件)并载入,就可以对转录组测序数据进行可视化浏览。对比samtools tview功能,这个软件有交互式操作界面,对萌新非常友好。","text":"IGV(Integrative Genomics Viewer)是一个非常方便的比对软件,在使用前只需要将参考基因组和bam文件分别建立索引(即建立fai和bai文件)并载入,就可以对转录组测序数据进行可视化浏览。对比samtools tview功能,这个软件有交互式操作界面,对萌新非常友好。 1. IGV软件下载直接上百度搜就能找到IGV官网,选择linux版本或者windows版本都行,这里用linux版本为例,IGV只支持JAVA11版本,不用担心这个问题,下载的安装包里直接有JAVA11,解压就可以用,就是国外网站下载有点慢(科学上网)。 直接在虚拟机里解压打开,运行igv.sh,会自动准备好JAVA11的运行环境,成功弹出交互式界面(终于告别了黑漆漆的命令行 )。 2. 导入文件Genomes菜单栏上传建立索引的参考基因组.fa和.fai文件: File菜单栏上传排序并建立索引的.bam和.bai文件: 如果有参考基因组注释文件,同样可以导入进去,同样导入前需要sort排序和建立index,可以用菜单栏里的igvtools直接sort和index: 3. 界面解读我导入了5组bam数据,所有文件导入后可以看到如下界面,简单介绍一下各个区域和功能: 主页面获得的信息有限,我们选取第3条染色体为例,将其放大: 中间的界面可以通过左右拖动鼠标,或者按左右方向键来浏览染色体上的比对情况。我们在搜索框中直接搜基因名字进行染色体定位,比如CIPK家族中的CIPK7基因,回车后双击最后一栏基因注释文件中的基因名称CIPK7,可以得到详细的CIPK7基因信息(这里注意下,如果双击弹出来多个可供选择的片段的话,代表这个基因存在可变剪切): 在基因注释区右键,选择expanded,可以将CIPK7基因的所有转录本显示出来。 放大到一定程度后,我们可以看到基因注释区上方出现了核苷酸序列和氨基酸序列,我们可以点击sequence旁边的箭头,切换到另一条链的序列。 点击核苷酸,会出现三行,分别表示不同起始位点的核苷酸翻译结果,绿色为起始密码子,红色的星号表示终止密码子。 再来看看放大后的tracks区域,bam文件在载入后会默认生成两个tracks,一个显示测序深度(Coverage track,可以对比下samtools depth),一个显示比对情况(Alignment track),我们放大其中一个样本的数据信息。 Coverage track区域灰色代表质量好,如果reads中某核苷酸与参考序列超过20%不一致的时候,IGV会根据四个碱基的计数对coverage的条形图进行着色。这里可以看到该位点处有20个reads覆盖到,8条reads测的是C核苷酸,12条reads测的是T核苷酸。如果某个位置coverage条形图只有一种颜色,即该位点测的核苷酸和参考序列完全不一样,那说明该位点是SNP位点。 Alignment tracks柱形图是和bam文件中的数据一一对应的,举个例子,我在IGV软件的ERR1698206.bam可以看到在第3条染色体位置8173028有3条reads。虚拟机中找到这个bam文件,直接samtools view查看并grep这个位置,可以找到3条定位的reads(还有三条是配对的另一条链)。 如果一条reads中间有缺失,IGV会用黑色横杠表示,中间数字表示缺失几个核苷酸。 IGV还用不同颜色标记异常的插入片段大小的reads,这里做的是RNA-seq数据比对,不用看reads颜色,有些reads还在质控的时候被裁短了,变成蓝色很正常(因为比预期短,个人理解是这样,有待考证?)。以下是官网的默认着色方案:","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"转录组数据分析","slug":"转录组数据分析","permalink":"http://www.shelven.com/categories/%E8%BD%AC%E5%BD%95%E7%BB%84%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"}],"tags":[{"name":"IGV","slug":"IGV","permalink":"http://www.shelven.com/tags/IGV/"}]},{"title":"转录组数据分析笔记(3)——samtools用法小结","slug":"转录组数据分析笔记(3)——samtools用法小结","date":"2022-04-16T10:41:24.000Z","updated":"2022-12-03T16:37:26.000Z","comments":true,"path":"2022/04/16/a.html","link":"","permalink":"http://www.shelven.com/2022/04/16/a.html","excerpt":"本篇笔记主要记录samtools的用法。","text":"本篇笔记主要记录samtools的用法。 1. sam文件转化bambam文件是二进制文件,占用磁盘空间小,运算速度快,samtools操作是针对bam文件的,所以我们要进行数据转化。samtools sort指令可以将bam文件进行排序,这个指令同时也可以将sam文件转化成bam文件: 12345#!/bin/bashls *.sam | while read iddo samtools sort -l 0 -@ 5 -o $(basename $id ".sam").bam $id # 指定输出文件,改后缀.bamdone 运行脚本,将当前目录的sam文件全转换成bam文件并排序(这里的排序不是按名称,用HTseq还要再按照read名称排序,使用参数-n)。 samtools sort参数samtools sort # 对bam文件进行排序(sam文件排序不会变)-l # 设置输出文件压缩等级,0-9,0是不压缩,9是最高等级压缩-@ # 设置线程数-o # 设置排序后输出的文件名最后接输入的bam或者sam格式文件 2. 构建索引文件2.1 构建bam文件索引在bam文件目录下,排序后的bam文件可以建立索引: $ ls *.bam | xargs -i samtools index {} 注意下xargs -i的用法,和管道不一样,是传递参数给后一个命令的花括号中,后一个命令中不存在歧义的时候可省略参数-i和花括号。 如图生成的bai文件就是索引文件。其实到了这一步,前面的sam文件就可以删除(节省电脑空间),只留下bam文件就行,bam文件无法直接查看,可以通过samtools view命令查看bam文件。 2.2 构建参考基因组fa文件索引在参考基因组文件目录下,对参考基因组的fa文件建立索引: $ samtools faidx Arabidopsis_thaliana.dna.genome.fa 参考基因组文件名注意改成自己的,生成的索引文件是.fai结尾的 3. bam文件qc质控samtools转化生成的bam文件需要进行质控,看看比对情况如何。在bam文件目录下,我们创建一个samtools自带qc质控指令samtools flagstat运行脚本: 12345#!/bin/bashls *.bam | while read iddo samtools flagstat -@ 4 $id > $(basename $id ".bam").flagstat # 自定义输出文件done $ samtools flagstat bam文件 > 输出文件 # 这种格式,其他参数都一样 运行脚本文件可以获得16个.flagstat质控文件,和fastqc一样,我们还可以做完后用multiqc命令集合成一个html格式的总的qc报告网页。和fastqc不同之处是,fastqc是做下机数据质控,samtools是做比对参考基因组的质控。如下图所示,可以比较直观地看出大部分reads都是map上的。 生成的每一个flagstat文件我们也可以直接点开。 每一行统计数据都是以通过QC的reads数量和未通过QC的reads数量组成,以我点开的这个文件为例,主要信息有以下几个: 13992629个reads都是合格的12328290个reads只比对到参考基因组一个位置上13988737个reads比对到参考基因组(99.97%)12332182个reads是成对的12201338个reads可以正确配对(98.94%)2846条reads成对但只有一条能比对上参考基因组12398个配对的reads可以比对到别的染色体上 可以自己将所有的flagstat运行结束后的文件放在一个目录下,运用paste命令全部按列粘贴在一起,用cut或者awk提取所需的列数据自己做比对情况表格,这里不再赘述。 4. samtools其他指令简单介绍一下: $ samtools view ERR1698194.bam #查看bam文件(不能直接cat查看二进制文件) $ samtools tview ERR1698194.bam #类似于IGV这种基因组浏览器,但是非交互式界面(下图)不直观,我们一般都是用IGV查看基因组 其他还有samtools merge(合并所有bam文件到一个文件),samtools depth(得到每个碱基位点或者区域的测序深度,并输出到标准输出)等等,不是特别常用,这里就不介绍了。 在步骤2中构建的索引文件可以导入IGV中,对转录组每个read mapping情况进行可视化浏览,下个笔记将介绍IGV的用法。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"转录组数据分析","slug":"转录组数据分析","permalink":"http://www.shelven.com/categories/%E8%BD%AC%E5%BD%95%E7%BB%84%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"}],"tags":[{"name":"samtools","slug":"samtools","permalink":"http://www.shelven.com/tags/samtools/"}]},{"title":"转录组数据分析笔记(2)——使用Hisat2比对参考基因组","slug":"转录组数据分析笔记(2)——使用Hisat2比对参考基因组","date":"2022-04-15T08:53:41.000Z","updated":"2022-12-03T16:38:55.000Z","comments":true,"path":"2022/04/15/a.html","link":"","permalink":"http://www.shelven.com/2022/04/15/a.html","excerpt":"本片笔记主要记录Hisat2的用法,以及比较四个常用的比对参考基因组的软件。","text":"本片笔记主要记录Hisat2的用法,以及比较四个常用的比对参考基因组的软件。 1. 建立参考基因组索引在进行clean data与参考基因组比对之前,我们需要先建立参考基因组索引。进入下载好参考基因组的文件目录下,运行命令: $ hisat2-build Arabidopsis_thaliana.dna.genome.fa genome -p # 以几个线程运行,与电脑核数或者分配虚拟机的核数有关genome # 命名的索引文件名,可以改成自己能找到的 就可以在当前目录建立参考基因组索引文件,hisat2固定会生成8个以.ht2做后缀名的索引文件,如下所示: 需要注意的一点,比对软件除了hiasat2以外,还有subjunk、bwa、bowtie2等等,各个比对软件生成的索引文件是不同的,不能相互混用,命名的时候注意区分各种比对工具。 2. clean data与参考基因组比对比对的意思是将每一个read与参考基因组序列进行对比,目的是得到每一个read在参考基因组上的位置信息,有了这个基础的位置信息才可以进行后续基因或者转录本的定量,最终由定量结果做差异表达矩阵,分析上调或者下调的基因数量。 新建一个shell脚本输入下面的代码 12345678#!/bin/bashfor i in `seq 194 209`do hisat2 -p 4 -x /media/sf_example/data/ref/genome \\ #索引文件绝对路径 -1 /media/sf_example/data/clean_data/ERR1698"$i"_1.fq.gz \\ -2 /media/sf_example/data/clean_data/ERR1698"$i"_2.fq.gz \\ -S /media/sf_example/data/hisat2_sam/ERR1698"$i".sam #注意大写的Sdone 参数解释: -p # 同样是配置线程数-x # 指定索引文件,需要定义索引文件名称,不能加后缀,不能只定义到索引文件所在目录-1 # 第一端测序数据文件-2 # 第二端测序数据文件-S # 指定输出目录和文件,不指定会刷屏,注意是大写的S 输出到屏幕的结果如下,我们选取其中一个进行解读: 共有6166091对测序数据,都是双侧测序数据,其中: read1 和 read2 没有合理比对上参考基因组序列的有65259对,占1.06% read1 和 read2 只有一条比对上参考基因组序列的有5698903对,占92.42%,这部分reads数需要占测序reads的绝大多数才正常 read1 和 read2 可以同时比对到多个地方的有401929对,占6.52% 65259对没有合理比对上的序列中,55871对可以不合理地比对上一次 最后一块是对两条链拆开比对的结果,这个一般用不到,本来测序的两条reads就应该比对到同一个染色体同一个基因附近,拆开比对到不同染色体没有意义。我们要看的是最后一句话,总比对率为99.97%,通常比对率大于90%说明比对情况较好,与参考基因组基本吻合。 3. sam文件解读比对结果除了有屏幕上输出的总体报告外,还有记录详细比对结果的sam文件。双端测序的比对会将两个测序文件进行整合和比较,最后只生成一个sam文件,因此这个sam文件非常大,hisat2比对生成的sam文件可以直接打开。我们可以选取一部分进行解读。 @HD VN:1.0 SO:unsorted (排序类型) VN是格式版本;SO表示比对排序的类型,有unknown,unsorted,queryname和coordinate几种。samtools软件在进行行排序后不能自动更新sam文件的SO值。 @SQ SN:1 LN:30427671 (序列ID及长度) 参考序列名,这些参考序列决定了比对结果sort的顺序,SN是参考序列名;LN是参考序列长度;每个参考序列为一行。这里表示拟南芥有5条染色体,对应长度都在后面,Mt是线粒体基因,Pt是叶绿体基因。 @PG ID:hisat2 PN:hisat2 VN:2.2.1 (比对所使用的软件及版本) 这里包括了路径,方法,以及我质控后的序列长度(50-100)等详细信息。 接下来每行都是一长串,显示的是比对结果部分,11个字段(列) 第一列:QNAME:测序出来的reads序列数据名,ERR1698194.2 第二列:FLAG:表明比对类型:paring,strand,mate strand等 第三列:RNAME:参考基因组的染色体名,我这里是第1条染色体 第四列:POS:比对到这个染色的具体位置,4969 第五列:MAPQ:比对质量,是一个衡量比对好坏的打分结果,60最好 第六列:CIGAR:简要比对信息表达式,1S100M是第1个碱基切除,100个匹配 第七列:RNEXT:另一个序列比对上的参考序列编号,没有另外的片段是*,同一个片段= 第八列:MPOS:另一个序列匹配的染色体具体位置,这里一样也是4969 第九列:TLEN:配对片段长度,最左边的为正,最右边的为负 第十列:SEQ:和参考序列在同一个链上比对的序列 第十一列:QUAL:比对序列的质量和reads碱基质量值 后面提供额外的信息,一般不重要,了解一下就行。因为sam文件太大(往往有10G以上),也不适合电脑进行后续处理,所以我们会用到samtools,将sam文件转化为更适合电脑处理的二进制bam文件。这个后面会讲。 4. 其他比对软件以下4种软件均用于序列比对,用法稍有不同,做个记录 $ hisat2 -p 4 -x 索引目录 -1 单端测序数据文件 -2 另一端测序数据文件 -S 输出文件 $ subjunk -T 4 -i 索引目录 -r 单端测序数据文件 -R 另一端测序数据文件 -o 输出文件 $ bowtie2 -p 4 -x 索引目录 -1 单端测序数据文件 -2 另一端测序数据文件 -S 输出文件 $ bwa mem -t 4 -M 索引目录 单端测序数据文件 另一端测序数据文件 > 输出文件","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"转录组数据分析","slug":"转录组数据分析","permalink":"http://www.shelven.com/categories/%E8%BD%AC%E5%BD%95%E7%BB%84%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"}],"tags":[{"name":"hisat2","slug":"hisat2","permalink":"http://www.shelven.com/tags/hisat2/"}]},{"title":"转录组数据分析笔记(1)——如何用fastqc和trim-galore做测序数据质控","slug":"转录组数据分析笔记(1)——如何用fastqc和trim-galore做测序数据质控","date":"2022-04-14T13:13:35.000Z","updated":"2022-12-03T16:39:16.000Z","comments":true,"path":"2022/04/14/a.html","link":"","permalink":"http://www.shelven.com/2022/04/14/a.html","excerpt":"本系列学习笔记数据均来自”Temporal dynamics of gene expression and histone marks at the Arabidopsis shoot meristem during flowering“,原文用RNA-Seq的方式研究开花阶段,芽分生组织不同时期的基因表达量变化,4个时间段(0, 1, 2, 3),4个重复,共有16个样品。点击这里获取文献","text":"本系列学习笔记数据均来自”Temporal dynamics of gene expression and histone marks at the Arabidopsis shoot meristem during flowering“,原文用RNA-Seq的方式研究开花阶段,芽分生组织不同时期的基因表达量变化,4个时间段(0, 1, 2, 3),4个重复,共有16个样品。点击这里获取文献 1. 读文章获得RNA-Seq数据从文章末尾我们可以获得一些测序数据信息: Data availability. ChIP-seq and RNA-seq data have been deposited with ArrayExpress database (www.ebi.ac.uk/arrayexpress), accession numbers E-MTAB-4680, E-MTAB-4684 and E-MTAB-5130. 可以看到作者将CHIP-seq和RNA-seq数据上传到ArrayExpress这个数据库中,这个数据库是欧洲生物信息研究所(European Bioinformatics Institute, EBI)旗下的公共数据库,主要用于存放芯片和高通量测序数据,我们可以直接从该数据库中下载我们需要的RNA-seq数据,自己动手分析。 顺便介绍一下,欧洲EBI旗下的ENA数据库,美国NCBI旗下的GenBank,以及日本的DDBJ三大巨头组成了国际核酸序列数据库合作联盟(INSDC),这三大数据库收录了世界上报道的所有序列数据。 EBI数据库可以直接下载fastq数据,不需要做SRA数据转换(NCBI数据库中下载sra数据则需要转换,需要用工具fastq-dump),这是EBI数据库下载高通量测序数据的优点,但是这个数据库经常网络连接不稳定,用aspera或者prefetch这种高速下载软件也不一定能稳定下载 最好的方法是科学上网。我们可以从ArrayExpress数据库中输入索引号E-MTAB-5130,直接获得样本信息和测序信息。 2. 测序数据质控我们可以看到,下载的数据是双端测序产生的。我们不能直接用下载的raw data做后续分析,必须要进行质控查看测序质量如何。 2.1 使用fastqc对测序数据生成质控报告下载好的fastq文件可以直接用fastqc工具做测序数据质控,输入以下命令一次生成所有qc报告: $ fastqc *.fastq.gz -o ./ #在当前目录下对所有.fastq.gz文件生成qc报告,-o参数定义输出目录 运行结束后我们可以得到.html文件和.zip压缩包,这个就是质控报告。在虚拟机里,我们可以直接点开.html后缀的网页文件查看质控报告(和压缩包的内容是一致的)。 顺便介绍一个非常好用的工具multiqc,可以通过conda install直接安装,这个工具可以将批量生成的qc报告合并为一个,看起来更加直观。在生成qc报告的当前目录下,运行代码: $ multiqc ./ 2.2 质控报告解读2.2.1 基本信息绿色表示通过,黄色表示不太好,红色表示不通过。RNA-seq一般在Sequence Duplication Levels上结果会不好,一个基因可能会大量表达,测到好多遍。 2.2.2 核苷酸测序质量箱式图这里测序质量(纵坐标)用Q值表示,p为出错率,Q值计算式为Q=-10*lg(p)。每一个核苷酸的测序质量可以从fastq文件第四行一一对应上,这里只是做了统计和可视化。我们可以看到每个位点的核苷酸测序质量Q值都在30以上,意味着每个位点的测序正确率都在99.9%以上,可以认为测序质量比较好。 箱式图解读:黄色箱子(25%和75%的分数线),红色线(中位数),蓝线是平均数,下面和上面的触须分别表示10%和90%的点。 2.2.3 测序泳道质量图纵坐标为tile编号,这张图代表每次荧光扫描的质量。蓝色背景表明测序质量良好,白色和红色的背景表示测序过程中可能有小气泡或者测序泳道上有污染。直接的体现就是部分测序数据中出现连续的N,也就是不能读取,可能是任何一个核苷酸。 2.2.4 reads质量得分可以看到平均质量在38,质量比较高。如果最高峰所对应的横坐标质量值小于27(错误率0.2%) 则会显示“警告”,如果最高峰的质量值小于20(错误率1%)则会显示“不合格”。 2.2.5 每条reads各个测序位点上各碱基出现概率图上看得出比较稳定,测序刚开始的时候波动会大一点,这里的GC含量和AT含量不一致。如果任何一个位置上的A和T之间或者G和C之间的比例相差10%以上则报“警告”,任何一个位置上的A和T之间或者G和C之间的比例相差20%以上则报“不合格”。 2.2.6 GC含量和理论分布可以看出GC含量在43%左右,与理论分布(也就是正态分布)比较吻合,中心峰值与所测转录组的GC含量一致。如果有不正常的尖峰,可能是测序文库有污染,接头的污染还会在过表达序列中体现。 2.2.7 每条reads的含N碱基数不能识别的碱基会被读成N,这里没有N,测序质量非常好。横坐标表示reads的位置,纵坐标表示N的比例。如果任何一个位置N的比例大于5%则报“警告”,大于20%则报“失败”。 2.2.8 测序长度分布这个测序仪一次测量长度是101bp。测序仪出来的原始reads通常是均一长度的,经过质控软件处理过的reads长度则不一样,这里说明测序结果较好。 2.2.9 重复序列水平可以看到重复水平较低。图中横轴代表reads的重复次数,大于10次重复后则按不同的重复次数合并显示。纵坐标表示各重复次数下的reads数占总reads的百分比;蓝线展示所有reads的重复情况,红线表示在去掉重复以后,原重复水平下的reads占去重后reads总数的百分比;如果非unique的reads占总reads数的20%以上则报 ”警告“,占总read数的50%以上则报 ”不合格“。这项变黄是正常的。 2.2.10 过表达序列和接头序列过表达的序列很可能是一些测序的接头序列,这里两种序列都看不到,说明质量良好。过表达序列是显示同一条reads出现次数超过总测序reads数的0.1%的统计情况,超过0.1%则报“警告”,超过1%则报“不合格”,会列出可能的接头序列。接头序列正常情况下含量接近于0。 2.3 trim-galore测序数据质控过滤质控的目的使为了除去下机数据raw data中的接头序列和质量比较差的测序数据,Q<20,正确率小于99%,如果这样的核苷酸超过read长度的20%,则考虑将该read丢弃(只是建议,不是强制,根据需要可以自定义过滤条件)。 trim-galore也可以用conda install安装,非常方便,这是一个自动检测adaptor的软件,可以一个命令自动找出主流的测序接头并去除,还可以设置参数对测序数据质控。简单介绍一下trim-galore的一些参数: -q # 设定Phred quality score阈值,默认为20;-phred33 # 测序平台衡量测序质量的方法,有33和64,不影响;-length # 设定输出reads长度阈值,小于设定值会被抛弃,根据需要设计;-stringency # 设定可以忍受的前后adapter重叠的碱基数,默认为1(非常苛刻);-paired # 用于分析双端测序数据结果;-o # 输出目录 因为是双端测序,16个样本每个都有_1和_2两个文件,可以写个脚本批量运行: 12345678#!/bin/bashfor i in `seq 194 209` do trim_galore -q 25 -phred33 -length 50 -stringency 3 -paired \\ -o /media/sf_/example/data/clean_data \\ /media/sf_/example/data/raw_data/ERR1698"$i"_1.fastq.gz \\ #一端测序数据 /media/sf_/example/data/raw_data/ERR1698"$i"_2.fastq.gz #另一端测序数据done 保存退出,运行,最后生成的_triming_report.txt文件就是生成的质控报告,_val_1.fq.gz就是过滤后瘦身的clean data,我们可以看到大小比原来小了10M左右,这个clean data才可以用于后续的分析流程 我截取了其中一个数据的质控结果,拉到最底下,可以看到两端测序数据中都有AGATCGGAAGAGC这个序列,在一个样本测序数据中出现240027次经过网上查找,AGATCGGAAGAGC这个序列确实是Illumina公司测序时的接头序列(点击这里查看),可以和上面fastqc质控报告中的测序平台Illumina相互验证。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"转录组数据分析","slug":"转录组数据分析","permalink":"http://www.shelven.com/categories/%E8%BD%AC%E5%BD%95%E7%BB%84%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"}],"tags":[{"name":"fastqc","slug":"fastqc","permalink":"http://www.shelven.com/tags/fastqc/"},{"name":"multiqc","slug":"multiqc","permalink":"http://www.shelven.com/tags/multiqc/"},{"name":"trim-galore","slug":"trim-galore","permalink":"http://www.shelven.com/tags/trim-galore/"}]},{"title":"小破站正式对外开放啦!","slug":"小破站正式对外开放啦!","date":"2022-04-13T14:29:14.000Z","updated":"2023-07-04T10:13:40.000Z","comments":true,"path":"2022/04/13/a.html","link":"","permalink":"http://www.shelven.com/2022/04/13/a.html","excerpt":"咳咳,经过10天左右紧张地准备,小站今天正式对外开放啦!作为第一次运行个人网站的小白,看着网站从零开始在自己手上慢慢展现一个个页面,实现一个个功能,这种成就感和激动感,让我感觉这几天的熬夜狂肝还是值得的呜呜呜我的头发。","text":"咳咳,经过10天左右紧张地准备,小站今天正式对外开放啦!作为第一次运行个人网站的小白,看着网站从零开始在自己手上慢慢展现一个个页面,实现一个个功能,这种成就感和激动感,让我感觉这几天的熬夜狂肝还是值得的呜呜呜我的头发。 建站过程为什么建站说是从零开始,其实也是站在前人搭建好的框架上才能顺利进行的。我很早之前就萌发了搭建个人网站的想法,自从这个学期开始学习生物信息学,我也慢慢对linux系统有了更深入的理解。一开始只是在虚拟机上跑跑程序,后来就想着不如买一个云服务器装linux玩玩,既然买了服务器了,那就再绑个域名吧,既然两个都有了,不如就再建个网站吧(滑稽)。于是趁着腾讯云的轻量级应用服务器打折的机会,一次性买了3年…然后又在阿里云买了个域名,了解到需要备案后才能解析域名,行,又办理各种手续在工信部备了案。不得不说,在各大云服务器商内卷搞活动的时候,有个学生认证还是相当香的。至于怎么用服务器,那就是后面考虑的事了。 备案和备案期间的学习我在腾讯云买的服务器,通过接入商腾讯云协助,腾讯云先审核我的材料,通过以后再提交工信部备案,备案还是相当快的,3天时间就办下来了。备案期间也没闲着,作为一个前端小白的我,又去恶补了一些前端知识,比如什么是css、js、ejs、html文件,这些文件的格式是怎么样的,java的一些基本语法等等。学习的折磨程度不亚于刚开始学R语言和linux操作系统,不过有了一些shell脚本的语法知识以后,还是能感觉到这些语言之间还是有共同的判断方式和逻辑在里面的(纯小白发言,不知道对不对)。在慢慢摸索的过程中痛并快乐着,先是照着别人给的js文件魔改,再是自己调试遇到的问题和bug,尤其在发现bug最后解决bug的时候,那种成就感能给我带来莫大的快乐。 建站历程建站的过程是痛苦的,踩了非常多的坑,我觉得我甚至可以写好几篇攻略出来。我一开始的想法是在github建库搭建个人网站,从安装nodejs和npm这种最基础的开始,配置环境,用hexo框架搭建一个本地静态博客,然后部署到github空间,这样就可以用github仓库名访问我的网站。但是有一个非常大的问题,github从国内访问会有DNS污染,连接速度那叫一个绝望。我自己是可以科学上网,但是总不能让别人浏览我网页的时候也科学上网吧?我也不太相信有很多人会用改host的办法来访问github,于是我就萌生了将买的云服务器用来搭建网站的想法(我知道这是一种资源浪费),github就可以当做网站的备份,以后即使我的云服务器过期了,我也可以依旧正常访问搭建在github里的静态博客。所以我的部署过程有点绕,就是本地生成静态博客,先部署到github仓库,再同步部署到我的云服务器。这样我就可以用备案后的二级域名解析到云服务器,在通过安装httpd服务来开启外部的访问了。 可以访问我的网站还是第一步,还要做好安全防护,申请SSL安全证书才能开启https连接。免费申请方式也很多,我申请了一年的apache上的SSL安全证书,然后安装到自己服务器上。还想吐槽一下,腾讯云有一键部署SSL安全证书通道,要90块钱,只要有点linux文本操作基础,自己按照教程部署一下半小时左右就能完成,这钱真好赚。SSL证书安装做好以后,就可以上别的云服务商找找免费的CDN加速了,有CDN加速一是可以加快网站的加载速度,二是隐藏自己服务器的ip地址,能起到一定的网站安全防护作用。吹一波又拍云,只要在网站底下加上他们的标志,启用他们的CDN加速,就能申请加入又拍云联盟,有免费一年的CDN加速和云储存服务,还可以查看访问记录等等 学生党薅羊毛的利器23333 。因为我的网页图片比较多,所以就应用了网页图片加速。 后记具体过程比如怎么接入第三方各种网站,用什么主题,怎么美化页面等等,就不详细说了,说多了肝疼,以后有想法再更新如何从零开始搭建自己的博客吧!至少没有服务器和域名也是完全可以实现的。建立这个小破站也主要是为了上传自己的学习笔记,整理生信网站和工具合集(相应的栏目还在建设中 新建文件夹了 ),督促自己学习hhhhh 本人技术实力有限,也不想搞地太花里胡哨,之后可能会有一些简单的小功能接入,还有移动端浏览小破站的优化(现在移动端浏览这个小破站简直是灾难,我都看不下去了),太费心思的东西就暂时放放了,主要专注于内容的创作,这几天会把一些学习笔记陆续上传。本人也是第一次用markdown语法写东西,排版一直搞不定段首的两个空格,先这样吧。 开摆","categories":[{"name":"个人主页","slug":"个人主页","permalink":"http://www.shelven.com/categories/%E4%B8%AA%E4%BA%BA%E4%B8%BB%E9%A1%B5/"}],"tags":[{"name":"建站","slug":"建站","permalink":"http://www.shelven.com/tags/%E5%BB%BA%E7%AB%99/"}]}],"categories":[{"name":"QQ机器人","slug":"QQ机器人","permalink":"http://www.shelven.com/categories/QQ%E6%9C%BA%E5%99%A8%E4%BA%BA/"},{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"},{"name":"编程自学","slug":"编程自学","permalink":"http://www.shelven.com/categories/%E7%BC%96%E7%A8%8B%E8%87%AA%E5%AD%A6/"},{"name":"群体基因组学","slug":"群体基因组学","permalink":"http://www.shelven.com/categories/%E7%BE%A4%E4%BD%93%E5%9F%BA%E5%9B%A0%E7%BB%84%E5%AD%A6/"},{"name":"三维基因组学","slug":"三维基因组学","permalink":"http://www.shelven.com/categories/%E4%B8%89%E7%BB%B4%E5%9F%BA%E5%9B%A0%E7%BB%84%E5%AD%A6/"},{"name":"单细胞转录组","slug":"单细胞转录组","permalink":"http://www.shelven.com/categories/%E5%8D%95%E7%BB%86%E8%83%9E%E8%BD%AC%E5%BD%95%E7%BB%84/"},{"name":"比较基因组学","slug":"比较基因组学","permalink":"http://www.shelven.com/categories/%E6%AF%94%E8%BE%83%E5%9F%BA%E5%9B%A0%E7%BB%84%E5%AD%A6/"},{"name":"个人主页","slug":"个人主页","permalink":"http://www.shelven.com/categories/%E4%B8%AA%E4%BA%BA%E4%B8%BB%E9%A1%B5/"},{"name":"网络相关","slug":"网络相关","permalink":"http://www.shelven.com/categories/%E7%BD%91%E7%BB%9C%E7%9B%B8%E5%85%B3/"},{"name":"深度学习","slug":"深度学习","permalink":"http://www.shelven.com/categories/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/"},{"name":"转录组数据分析","slug":"转录组数据分析","permalink":"http://www.shelven.com/categories/%E8%BD%AC%E5%BD%95%E7%BB%84%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"}],"tags":[{"name":"qq bot","slug":"qq-bot","permalink":"http://www.shelven.com/tags/qq-bot/"},{"name":"go-cqhttp","slug":"go-cqhttp","permalink":"http://www.shelven.com/tags/go-cqhttp/"},{"name":"Hi-C染色体挂载","slug":"Hi-C染色体挂载","permalink":"http://www.shelven.com/tags/Hi-C%E6%9F%93%E8%89%B2%E4%BD%93%E6%8C%82%E8%BD%BD/"},{"name":"3D-DNA","slug":"3D-DNA","permalink":"http://www.shelven.com/tags/3D-DNA/"},{"name":"JBAT","slug":"JBAT","permalink":"http://www.shelven.com/tags/JBAT/"},{"name":"juicer2","slug":"juicer2","permalink":"http://www.shelven.com/tags/juicer2/"},{"name":"python","slug":"python","permalink":"http://www.shelven.com/tags/python/"},{"name":"群体基因组学","slug":"群体基因组学","permalink":"http://www.shelven.com/tags/%E7%BE%A4%E4%BD%93%E5%9F%BA%E5%9B%A0%E7%BB%84%E5%AD%A6/"},{"name":"Tassel5","slug":"Tassel5","permalink":"http://www.shelven.com/tags/Tassel5/"},{"name":"Plink","slug":"Plink","permalink":"http://www.shelven.com/tags/Plink/"},{"name":"HiC-Pro","slug":"HiC-Pro","permalink":"http://www.shelven.com/tags/HiC-Pro/"},{"name":"HiCPlotter","slug":"HiCPlotter","permalink":"http://www.shelven.com/tags/HiCPlotter/"},{"name":"HiTC","slug":"HiTC","permalink":"http://www.shelven.com/tags/HiTC/"},{"name":"Seurat","slug":"Seurat","permalink":"http://www.shelven.com/tags/Seurat/"},{"name":"MCScanX","slug":"MCScanX","permalink":"http://www.shelven.com/tags/MCScanX/"},{"name":"共线性分析","slug":"共线性分析","permalink":"http://www.shelven.com/tags/%E5%85%B1%E7%BA%BF%E6%80%A7%E5%88%86%E6%9E%90/"},{"name":"TEsorter","slug":"TEsorter","permalink":"http://www.shelven.com/tags/TEsorter/"},{"name":"Barker3","slug":"Barker3","permalink":"http://www.shelven.com/tags/Barker3/"},{"name":"容器","slug":"容器","permalink":"http://www.shelven.com/tags/%E5%AE%B9%E5%99%A8/"},{"name":"singularity","slug":"singularity","permalink":"http://www.shelven.com/tags/singularity/"},{"name":"tRNAscan-SE","slug":"tRNAscan-SE","permalink":"http://www.shelven.com/tags/tRNAscan-SE/"},{"name":"Rfam/Infernal","slug":"Rfam-Infernal","permalink":"http://www.shelven.com/tags/Rfam-Infernal/"},{"name":"建站","slug":"建站","permalink":"http://www.shelven.com/tags/%E5%BB%BA%E7%AB%99/"},{"name":"RepeatModeler","slug":"RepeatModeler","permalink":"http://www.shelven.com/tags/RepeatModeler/"},{"name":"RepeatMasker","slug":"RepeatMasker","permalink":"http://www.shelven.com/tags/RepeatMasker/"},{"name":"GMATA","slug":"GMATA","permalink":"http://www.shelven.com/tags/GMATA/"},{"name":"TRF","slug":"TRF","permalink":"http://www.shelven.com/tags/TRF/"},{"name":"ChatGPT","slug":"ChatGPT","permalink":"http://www.shelven.com/tags/ChatGPT/"},{"name":"GATK","slug":"GATK","permalink":"http://www.shelven.com/tags/GATK/"},{"name":"QUAST","slug":"QUAST","permalink":"http://www.shelven.com/tags/QUAST/"},{"name":"BUSCO","slug":"BUSCO","permalink":"http://www.shelven.com/tags/BUSCO/"},{"name":"LAI","slug":"LAI","permalink":"http://www.shelven.com/tags/LAI/"},{"name":"NextPolish","slug":"NextPolish","permalink":"http://www.shelven.com/tags/NextPolish/"},{"name":"Racon","slug":"Racon","permalink":"http://www.shelven.com/tags/Racon/"},{"name":"NextDenovo","slug":"NextDenovo","permalink":"http://www.shelven.com/tags/NextDenovo/"},{"name":"Canu","slug":"Canu","permalink":"http://www.shelven.com/tags/Canu/"},{"name":"github","slug":"github","permalink":"http://www.shelven.com/tags/github/"},{"name":"内网穿透","slug":"内网穿透","permalink":"http://www.shelven.com/tags/%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F/"},{"name":"代理服务器","slug":"代理服务器","permalink":"http://www.shelven.com/tags/%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8/"},{"name":"爬虫","slug":"爬虫","permalink":"http://www.shelven.com/tags/%E7%88%AC%E8%99%AB/"},{"name":"requests","slug":"requests","permalink":"http://www.shelven.com/tags/requests/"},{"name":"Xpath","slug":"Xpath","permalink":"http://www.shelven.com/tags/Xpath/"},{"name":"selenium","slug":"selenium","permalink":"http://www.shelven.com/tags/selenium/"},{"name":"HTTP","slug":"HTTP","permalink":"http://www.shelven.com/tags/HTTP/"},{"name":"人工智能","slug":"人工智能","permalink":"http://www.shelven.com/tags/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/"},{"name":"google colab","slug":"google-colab","permalink":"http://www.shelven.com/tags/google-colab/"},{"name":"Tacotron2","slug":"Tacotron2","permalink":"http://www.shelven.com/tags/Tacotron2/"},{"name":"HiFiGAN","slug":"HiFiGAN","permalink":"http://www.shelven.com/tags/HiFiGAN/"},{"name":"拆包","slug":"拆包","permalink":"http://www.shelven.com/tags/%E6%8B%86%E5%8C%85/"},{"name":"声纹识别","slug":"声纹识别","permalink":"http://www.shelven.com/tags/%E5%A3%B0%E7%BA%B9%E8%AF%86%E5%88%AB/"},{"name":"语音转文本","slug":"语音转文本","permalink":"http://www.shelven.com/tags/%E8%AF%AD%E9%9F%B3%E8%BD%AC%E6%96%87%E6%9C%AC/"},{"name":"反向代理","slug":"反向代理","permalink":"http://www.shelven.com/tags/%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86/"},{"name":"blast","slug":"blast","permalink":"http://www.shelven.com/tags/blast/"},{"name":"kmergenie","slug":"kmergenie","permalink":"http://www.shelven.com/tags/kmergenie/"},{"name":"SOAPdenovo2","slug":"SOAPdenovo2","permalink":"http://www.shelven.com/tags/SOAPdenovo2/"},{"name":"jellyfish","slug":"jellyfish","permalink":"http://www.shelven.com/tags/jellyfish/"},{"name":"GenomeScope2.0","slug":"GenomeScope2-0","permalink":"http://www.shelven.com/tags/GenomeScope2-0/"},{"name":"blast+","slug":"blast","permalink":"http://www.shelven.com/tags/blast/"},{"name":"nanoplot","slug":"nanoplot","permalink":"http://www.shelven.com/tags/nanoplot/"},{"name":"filtlong","slug":"filtlong","permalink":"http://www.shelven.com/tags/filtlong/"},{"name":"BLAST+","slug":"BLAST","permalink":"http://www.shelven.com/tags/BLAST/"},{"name":"aspera","slug":"aspera","permalink":"http://www.shelven.com/tags/aspera/"},{"name":"perl","slug":"perl","permalink":"http://www.shelven.com/tags/perl/"},{"name":"bioperl","slug":"bioperl","permalink":"http://www.shelven.com/tags/bioperl/"},{"name":"集群","slug":"集群","permalink":"http://www.shelven.com/tags/%E9%9B%86%E7%BE%A4/"},{"name":"slurm","slug":"slurm","permalink":"http://www.shelven.com/tags/slurm/"},{"name":"AnnotationHub","slug":"AnnotationHub","permalink":"http://www.shelven.com/tags/AnnotationHub/"},{"name":"GO/KEGG","slug":"GO-KEGG","permalink":"http://www.shelven.com/tags/GO-KEGG/"},{"name":"org.At.tair.db","slug":"org-At-tair-db","permalink":"http://www.shelven.com/tags/org-At-tair-db/"},{"name":"ffmpeg","slug":"ffmpeg","permalink":"http://www.shelven.com/tags/ffmpeg/"},{"name":"numpy","slug":"numpy","permalink":"http://www.shelven.com/tags/numpy/"},{"name":"pillow","slug":"pillow","permalink":"http://www.shelven.com/tags/pillow/"},{"name":"R语言","slug":"R语言","permalink":"http://www.shelven.com/tags/R%E8%AF%AD%E8%A8%80/"},{"name":"pheatmap","slug":"pheatmap","permalink":"http://www.shelven.com/tags/pheatmap/"},{"name":"SRA","slug":"SRA","permalink":"http://www.shelven.com/tags/SRA/"},{"name":"SRA Toolkit","slug":"SRA-Toolkit","permalink":"http://www.shelven.com/tags/SRA-Toolkit/"},{"name":"GEO","slug":"GEO","permalink":"http://www.shelven.com/tags/GEO/"},{"name":"vscode","slug":"vscode","permalink":"http://www.shelven.com/tags/vscode/"},{"name":"ggplot2","slug":"ggplot2","permalink":"http://www.shelven.com/tags/ggplot2/"},{"name":"ggrepel","slug":"ggrepel","permalink":"http://www.shelven.com/tags/ggrepel/"},{"name":"linux指令","slug":"linux指令","permalink":"http://www.shelven.com/tags/linux%E6%8C%87%E4%BB%A4/"},{"name":"shell脚本","slug":"shell脚本","permalink":"http://www.shelven.com/tags/shell%E8%84%9A%E6%9C%AC/"},{"name":"DESeq2","slug":"DESeq2","permalink":"http://www.shelven.com/tags/DESeq2/"},{"name":"dplyr","slug":"dplyr","permalink":"http://www.shelven.com/tags/dplyr/"},{"name":"HTseq","slug":"HTseq","permalink":"http://www.shelven.com/tags/HTseq/"},{"name":"stringtie","slug":"stringtie","permalink":"http://www.shelven.com/tags/stringtie/"},{"name":"prepDE.py3","slug":"prepDE-py3","permalink":"http://www.shelven.com/tags/prepDE-py3/"},{"name":"IGV","slug":"IGV","permalink":"http://www.shelven.com/tags/IGV/"},{"name":"samtools","slug":"samtools","permalink":"http://www.shelven.com/tags/samtools/"},{"name":"hisat2","slug":"hisat2","permalink":"http://www.shelven.com/tags/hisat2/"},{"name":"fastqc","slug":"fastqc","permalink":"http://www.shelven.com/tags/fastqc/"},{"name":"multiqc","slug":"multiqc","permalink":"http://www.shelven.com/tags/multiqc/"},{"name":"trim-galore","slug":"trim-galore","permalink":"http://www.shelven.com/tags/trim-galore/"}]} \ No newline at end of file +{"meta":{"title":"我的小破站","subtitle":"","description":"","author":null,"url":"http://www.shelven.com","root":"/"},"pages":[{"title":"","date":"2022-04-09T14:14:36.602Z","updated":"2022-04-09T14:14:36.598Z","comments":true,"path":"404.html","permalink":"http://www.shelven.com/404.html","excerpt":"","text":"404 访问的页面走丢了!(⊙_⊙) 可能是输入地址有误或该地址已被删除 如果确认地址无误,请踢我一脚马上来改 TAT"},{"title":"","date":"2022-04-13T13:37:18.783Z","updated":"2022-04-13T13:37:18.770Z","comments":true,"path":"about/index.html","permalink":"http://www.shelven.com/about/index.html","excerpt":"","text":"感谢小伙伴的来访! 小破站尚在搭建中,有好的想法请留言~ 本站成立的初衷是记录本人学习笔记,以及整合各种有用的生信网站和工具,方便查阅和学习。本人是前端小白指bug越修越多,如果本站有bug请留言,非常感谢!各位dalao高抬贵手,请不要DDOS攻击_(:з」∠)_"},{"title":"所有分类","date":"2022-04-13T09:37:32.168Z","updated":"2022-04-13T09:37:32.164Z","comments":true,"path":"categories/index.html","permalink":"http://www.shelven.com/categories/index.html","excerpt":"","text":"没有你感兴趣的?0.0 请留言提出你的宝贵意见~"},{"title":"","date":"2023-02-23T14:46:44.404Z","updated":"2023-02-23T14:46:42.000Z","comments":false,"path":"history/index.html","permalink":"http://www.shelven.com/history/index.html","excerpt":"","text":"2022-2-23 更新日志:修复bug 修复文章分类页和热门标签页跳转错误bug 2022-1-14 更新日志:修复bug和栏目更新 修复评论区显示错误bug 更新生信网站导航栏目 2022-12-11 更新日志:修复bug和栏目优化 本地创建API,修复看板娘显示bug 迁移资源,修复本站响应时间过长bug 优化底部播放器,解决两个播放器播放不同步问题 2022-12-4 更新日志:栏目新增和优化 新增网址导航栏人工智能板块 优化文章显示方式,修复摘要显示bug 2022-11-29 更新日志:栏目优化 优化结绳栏目,笔记全部归档 2022-09-17 更新日志:评论系统更换 由于不可抗力因素原评论系统数据已无法恢复,今后将吸取教训做好备份 评论系统重新部署到Vercel,本站不再部署任何项目到腾讯云开发 2022-05-24 更新日志:图床迁移 本站图床已全部迁移至本地服务器,出于稳定性考虑不再使用jsdelivr 优化和新增网址导航栏内容 2022-05-18 更新日志:新增栏目 新增网址导航栏目,优化导航栏 2022-04-29 更新日志:图床迁移 github图床已挂,2022年4月29日之后本站图床将逐渐迁移本地 2022-04-19 更新日志:图床迁移 优化相册栏目 背景图片图床迁移至本地 2022-04-18 更新日志:新增栏目 新增相册栏目 2022-04-16 更新日志:看板娘优化 看板娘模块显示优化 2022-04-15 更新日志:页面优化 网站页脚优化 2022-04-14 更新日志:bug修复 修复文章永久链接中文乱码bug,已更改永久链接格式 修复部分页面强制重新加载bug,修改了部分pjax代码 修复aplayer播放器切换页面后中断播放bug,同上 2022-04-13 更新日志:bug修复和CDN加速 修复主页轮播图片空白bug,修改了部分parallax代码,已优化图片加载方式 本站已加入又拍云联盟,站点已进行CDN加速 小破站正式对外开放! 2022-04-12 更新日志:服务器迁移 服务器迁移至本地 本站已绑定www.shelven.com域名 本站已安装SSL安全证书 2022-04-11 更新日志:界面优化 添加pjax插件,优化部分页面加载速度 百度统计维护,5月31日恢复接入 bug:暂时无法全站无刷新加载 2022-04-10 更新日志:功能添加 添加rss订阅功能 新增结绳栏目 新增站内搜索功能 接入百度统计 2022-04-09 更新日志:功能添加 新增live2D看板娘模块 接入腾讯云开发和twikoo评论系统 启用MFA,新增留言提醒和QQ邮箱头像抓取功能 2022-04-08 更新日志:页面美化 更改鼠标样式 更改侧边栏配置 新增网页加载条 2022-04-07 更新日志:页面美化和功能更新 重设导航栏 重设封面 更改右键菜单功能 2022-04-06 更新日志:功能添加 添加背景音乐功能,接入aplayer播放器 添加网站统计访客数功能,接入LeanCloud 添加文章字数统计功能 2022-04-05 更新日志:页面美化 导航栏设计和美化 卡片添加透明度 优化主题背景图片 2022-04-04 更新日志:小破站成立啦! 使用Hexo框架,volantis主题搭建网站 github建库,代码托管 建立图床,使用jsDelivr进行CDN加速"},{"title":"","date":"2023-07-04T10:04:18.318Z","updated":"2023-07-04T10:04:16.000Z","comments":true,"path":"more/404.html","permalink":"http://www.shelven.com/more/404.html","excerpt":"","text":"404 访问的页面走丢了!(⊙_⊙) 可能是输入地址有误或该地址已被删除 如果确认地址无误,请踢我一脚马上来改 TAT"},{"title":"","date":"2022-04-13T07:44:09.805Z","updated":"2022-04-13T07:44:09.801Z","comments":true,"path":"mylist/index.html","permalink":"http://www.shelven.com/mylist/index.html","excerpt":"","text":""},{"title":"所有标签","date":"2022-04-13T09:37:59.531Z","updated":"2022-04-13T09:37:59.527Z","comments":true,"path":"tags/index.html","permalink":"http://www.shelven.com/tags/index.html","excerpt":"","text":"没有你感兴趣的?0.0 请留言提出你的宝贵意见~"},{"title":"壁纸分享(点击查看大图)","date":"2022-04-19T06:40:53.941Z","updated":"2022-04-19T06:40:53.937Z","comments":true,"path":"photo/index.html","permalink":"http://www.shelven.com/photo/index.html","excerpt":"","text":""},{"title":"实时微博热搜榜","date":"2022-04-27T18:04:09.706Z","updated":"2022-04-27T18:04:09.701Z","comments":true,"path":"weibo/index.html","permalink":"http://www.shelven.com/weibo/index.html","excerpt":"","text":""},{"title":"","date":"2022-12-01T15:33:37.918Z","updated":"2022-12-01T15:33:36.000Z","comments":true,"path":"Bioinformatics/index.html","permalink":"http://www.shelven.com/Bioinformatics/index.html","excerpt":"生信网站快速导航 本页面暂时无法使用站内搜索 绝对不是我懒得改代码 搜索请直接使用ctrl + F","text":"生信网站快速导航 本页面暂时无法使用站内搜索 绝对不是我懒得改代码 搜索请直接使用ctrl + F 交换友链请参照下列格式并留言 名字|name: Phantom链接地址|link: http://www.shelven.com/头像地址|avatar: https://www.shelven.com/tuchuang/avatar.jpg描述|desc: 博学而笃志,切问而近思"},{"title":"","date":"2022-12-03T15:45:17.107Z","updated":"2022-12-03T15:45:15.000Z","comments":false,"path":"AI/index.html","permalink":"http://www.shelven.com/AI/index.html","excerpt":"人工智能快速导航 和前面生信网站导航一样,暂时无法使用站内搜索 这一页收藏机器学习和人工智能相关网站","text":"人工智能快速导航 和前面生信网站导航一样,暂时无法使用站内搜索 这一页收藏机器学习和人工智能相关网站 人工智能常用文档PyTorch相关 Github仓库 PyTorch官网 PyTorch中文文档 OpenCV相关 Github仓库 OpenCV官网 OpenCV中文论坛 OpenCV官方文档 Numpy相关 Github仓库 Numpy官网 Numpy官方文档 Pandas相关 Github仓库 Pandas中文官网 Matplotlib相关 Github仓库 Matplotlib官网 Matplotlib中文文档 常用软件源 清华大学开源软件镜像站 阿里巴巴开源镜像站 豆瓣镜像源 北京外国语大学开源软件镜像站 算力平台 Colaboratory(推荐) Kaggle(推荐) 深度学习雾计算平台 矩池云 数据集各种类型的数据存储库 Kaggle Datasets Google Dataset Search Papers With Code DatasetList 格物钛 飞桨AI Studio 计算机视觉(CV)相关 ImageNet MNIST IMDB-WIKI VisualData 自然语言处理(NLP)相关 M-AILABS Speech Dataset LJ Speech Dataset LibriSpeech ASR corpus 标贝中文标准女声音库 人工智能竞赛网站 Kaggle 和鲸社区 DataFountain 华为云开发者大赛 阿里云天池 交换友链请参照下列格式并留言 名字|name: Phantom链接地址|link: http://www.shelven.com/头像地址|avatar: https://www.shelven.com/tuchuang/avatar.jpg描述|desc: 博学而笃志,切问而近思"}],"posts":[{"title":"ATAC-seq和CUT&Tag技术原理和实验流程","slug":"ATAC-seq和CUT-Tag技术原理和实验流程","date":"2023-08-17T16:21:11.000Z","updated":"2023-08-17T16:24:01.000Z","comments":true,"path":"2023/08/18/a.html","link":"","permalink":"http://www.shelven.com/2023/08/18/a.html","excerpt":"前阵子去兰州开全国植物生物学大会,做实验的师弟问了我一个问题:什么是ATAC-seq?因为王茂军老师课题组有同学在做CUT&Tag,我脑海里第一个出现的也是CUT&Tag,两者的研究内容虽然不同,但是使用的分析方式是非常相似的。正好看到菲沙基因做过这两个技术的讲座,这篇博客就整理一下介绍这两个技术以及衍生技术的分析原理,加深下自己的理解,详细的分析流程留到以后需要做的时候再记录。","text":"前阵子去兰州开全国植物生物学大会,做实验的师弟问了我一个问题:什么是ATAC-seq?因为王茂军老师课题组有同学在做CUT&Tag,我脑海里第一个出现的也是CUT&Tag,两者的研究内容虽然不同,但是使用的分析方式是非常相似的。正好看到菲沙基因做过这两个技术的讲座,这篇博客就整理一下介绍这两个技术以及衍生技术的分析原理,加深下自己的理解,详细的分析流程留到以后需要做的时候再记录。 1. ATAC-SeqATAC-seq全称Assay for Transposase Accessible Chromatin with high-throughput sequencing,翻译为转座酶可及染色质的高通量测序分析,简单来说这个技术是运用转座酶获取开放染色质区,然后通过高通量测序技术和生物信息学挖掘相关的基因信息,解决生物学相关问题。 1.1 背景介绍什么是开放染色质?在前面介绍三维基因组的博客中,介绍了真核生物的染色质结构由低级到高级可以分为4种,染色体的基本结构单位是核小体,核小体串珠结构螺旋化(也就是不断地压缩折叠)形成了直径为30nm的染色质纤维,细胞核内大多数染色质都是以这种染色质纤维的形式存在的。 我们知道DNA的复制和转录过程需要将染色质紧密的结构打开(打开的过程与组蛋白乙酰化有密切联系,组蛋白乙酰化使组蛋白携带的正电荷减少,削弱了组蛋白与DNA结合的能力,从而使染色质区域的结构从紧密变得松散),这部分打开后结构疏松的染色质就是开放染色质(open chromatin),当染色质打开后,暴露的DNA序列就有足够的空间和转录因子(Transcription factors,TF)结合,进而调控基因的表达。 这种允许顺式调控元件和反式作用因子结合,也就是允许与染色质进行物理接触的程度就是染色质的可接近性(chromatin accessibility),也称为染色质可及性。 如何检测染色质开放区?简单说一下几个常用的检测染色质开放区的方法。 一种是传统的使用DNA酶的实验方法MNase-seq和Dnase-seq。两者的思路都是将开放染色质区的DNA用DNA酶酶切后进行高通量测序。前者用的是限制性外切酶,将不受核小体保护的区域切除,只留下核小体上缠绕的DNA序列;后者用的限制性内切酶,将受核小体保护的区域切除,留下核小体之间的序列。 另一种是基于酚氯仿抽提的技术FAIRE-seq技术,超声波破碎甲醛固定的染色质,酚氯仿抽提得到的上层水相认为是潜在的开放染色质区,针对抽提得到的片段化开放性染色质区进行建库测序。 还有一种就是经典的研究蛋白与DNA互作的ChIP-seq技术,因为需要制作对应的转录因子的抗体去拉DNA,所以该技术只能根据明确的转录因子来检测该转录因子与DNA的互作,局限性比较大,这里就提一下不放在一起比较了。 最后就是ATAC-seq技术,依赖改造的Tn5转座酶(转座DNA设计为测序接头)将测序接头引入染色质开放区,对酶切后的DNA片段进行富集,最后通过PCR扩增后进行高通量测序。另一方面,转座酶还可以切割开放区染色质附近的核小体间连接区DNA,简单来说可以得到MNase-seq和Dnase-seq两种技术的结果。 如下图所示,我们可以比较一下这几种检测染色质开放区技术的检测范围和灵敏度: 研究方法 细胞数量 获取方式 优点 缺点 DNase-seq 1*10^7 DNase Ⅰ切割不受保护的DNA序列 单碱基对的酶切位点分辨率 酶用量要准确控制,切割有偏好性 MNase-seq 1*10^7 MNase优先切割受保护的DNA序列 酶切特性有较高分辨率 酶用量要准确控制,切割有偏好性 FAIRE-seq 1*10^5~1*10^7 超声波打断染色质 无偏性分析,重复性好 信噪比低,数据解读困难 ATAC-seq 5*10^2~5*10^4 改造的Tn5转座酶切割开放区并插入测序接头 细胞需求量少,实验时间短,灵敏度高,重复性好 容易引入线粒体污染 1.2 实验流程整个实验流程可以分为6个步骤: 细胞悬液制备(500~50000个细胞,最难的一步) 细胞裂解,制备细胞核(完整性十分重要) Tn5酶切,37℃酶切孵育 DNA片段纯化,磁珠法回收 PCR扩增12-15个循环 上机测序 上面PCR扩增这一步的流程,可以看到Tn5酶切过程中会在上下游分别产生一个缺口,因此需要72℃延伸的过程来补平缺口。引入barcoded primer是为了区分不同的样品(单细胞测序的话是区分不同类型细胞)。 从上面的实验流程也可以看出,ATAC-seq中比较重要的步骤是细胞悬液制备和提取完整的细胞核,在细胞裂解和提取细胞核过程中,线粒体DNA可能会与染色体DNA一起被提取和处理,从而引入线粒体污染。线粒体是没有组蛋白保护的,容易被Tn5转座酶切割,同时线粒体的拷贝数也比染色体高很多,如果线粒体在实验过程中没有去除,很容易导致线粒体DNA被富集,影响染色体DNA测序深度和覆盖度。 1.3 生信分析获得下机数据后,就可以开始做上下游的生信分析。因为我自己没跑过这个流程,所以这里以菲沙基因提供的流程为例,着重介绍ATAC-seq流程中产生的图如何解读,以及我们可以做哪些下游分析。 下机数据预处理拿到下机数据后首先去接头(adapter trimming),然后比对到参考基因组(alignment),对比对后得到的bam文件进行过滤,具体而言是提取可靠比对、去除PCR重复(推荐用picard软件)、去除细胞器污染(主要是线粒体和叶绿体)这三步。这些处理方式都是老朋友了。 数据质量评估ATAC-seq数据质量评估主要是看两个图,一个是插入片段分布图(Fragment Insertion Size Distribution),一个是TSS富集峰图。 插入片段分布图 ATAC-seq的插入片段分布有着非常鲜明的特点,一般把<100 bp的片段区域称NFR(Nucleosome-Free Region)也就是无核小体区,这部分区域也是转座酶最容易切割的区域,每隔10.5 bp就有一个小齿,对应DNA螺旋一周的间距。200 bp有一个峰对应的是核小体单体的插入片段长度,再远点的400 bp和600 bp有两个小峰,对应核小体二聚体和核小体三聚体的插入片段长度。 TSS富集峰图 转录起始位点(Transcription Start Site,TSS)是没有核小体的,所以在ATAC-seq质控分析中,可以明显看到NFR在转录起始位点富集。以上图为例,我们选择TSS上下游3Kb的区域,NFR reads在TSS位点两侧有明显富集趋势。底下的热图也是同样的意思,每一行表示一个基因或者转录本,图中的红色区域也不一定要延伸到底,因为部分TSS可能没有在这个时期开放,这是很正常的现象。 这两个质控步骤可以先做第一个,第二个TSS富集峰图需要在peak calling和转换文件格式为.bw之后,使用Deeptools工具作图。 Reads Shifting质控后还有一个步骤是进行reads shifting,前面说过Tn5酶切过程中会在上下游产生一个缺口,因此需要将正链正向移动4bp,负链负向移动5bp。 这一步在ACAT-seq的原文中有做,不做的话对单碱基分辨率要求比较高的分析是有影响的(比如转录因子足迹分析,下面的Motif Analysis会说)。 Peak Callingpeak calling是后续所有分析的基础。简单来说,在将reads比对到参考基因组后,因为进行的是pair-end测序,一对reads之间的序列为一个fragment,统计每个碱基上fragment的数量作图,哪个地方fragment数量多,在统计图上就会显示出一个峰,也就是一个peak。peak calling的过程就是检测染色质开放区的fragment富集信号。 这一步用的软件有比较经典的MACS2,这个软件可以处理ChIP-seq、ATAC-seq、CUT&TAG等等的数据,需要调整不同的参数。与ChIP-seq不同,ATAC-seq的Tn5转座酶酶切的是染色质开放区域,在TF结合区域的DNA是拉不下来的(反映在峰图上是一个谷,ChIP-seq是一个峰),因此在调整峰值偏移(peak shift)的时候,一般用shift-extend的方法进行分析,ATAC-seq需要向外shift。如下图: 比如我们测序reads长度是150bp,两条reads的5‘端代表Tn5的酶切位点,我们需要向外shift 75bp,让酶切位点处于reads的中间位置,再进行peak calling。 用上面的软件进行peak calling后会生成bedGraph文件,也就是.bdg后缀的文件,是bed文件的一种扩展,可以在IGV基因组浏览器中打开(可能会比较卡),也可以借助UCSC的工具bedGraphToBigWig转成BigWig文件后(.bw后缀)再到IGV基因组浏览器中打开。还有一个重要的结果文件是.narrowPeak后缀的文件,也可以直接导入IGV。 以上上游分析的步骤可以参考ATAC-seq data analysis: from FASTQ to peaks | Yiwei Niu’s Note。 接下来是常见的一些下游分析的方法。 IDR Peak对于一个样本如果做了多个重复,就需要对样本的可重复性进行评估。可以用ENCODE项目的一个软件包Irreproducible Discovery Rate (IDR) ,导入两个样本的.narrowPeak结果文件后作图分析,这个软件的作用是评估重复样本间peak的一致性,生成的图如下: IDR算法同时考虑了peaks间的overlap和富集倍数的一致性。上面的图所有的点都是两个样本间相互overlap的peak,也就是都是可重复的,红点代表富集倍数是有差别的,黑点代表富集倍数是一致的,因此黑点数量越多越好。 Peak Annotation拿到peak后,如果想要知道这些peak在基因组的哪些地方分布功能是什么,就需要对peak进行注释,常用的有R包ChIPseeker。 Motif Analysis这一部分能做的分析还是相当多的,列举几个: 从头预测:预测新的motif,注释已存在的motif。软件:MEME+Tomtom Motif扫描:除了开放染色质区域,寻找其他序列所有motif的位置信息。软件:FIMO 转录因子富集:软件AME或者HOMER 转录因子足迹(TF FootPrint):转录因子占位效应(转录因子结合在DNA上,阻止了Tn5酶切,在开放染色质区域留下一个缺失的位置),注意要进行前面说的reads shifting。软件R/centipade 常用的motif数据库: PlantTFDB:PlantTFDB - Plant Transcription Factor Database @ CBI, PKU (gao-lab.org) JASPAR:JASPAR - A database of transcription factor binding profiles (genereg.net) MEME:Download Releases - MEME Suite (meme-suite.org) TRANSFAC:Gene Regulation (gene-regulation.com) HOCOMOCO:HOmo sapiens COmprehensive MOdel COllection (autosome.org) Nucleosome positioning核小体定位,从前面的插入片段分布图可以看出,在ATAT-seq文库中,核小体单体插入片段数量相比NFR明显少很多,但是也有一些软件比如NuleoATAC和HMMRATAC可以用于核小体的占位分析。 联合分析 ChIP-seq:由于转录因子的结合区域在染色质开放区,因此ATAC-seq的peak和ChIP-seq的peak之间存在部分重叠,因此这两个组学联用可以相互验证,而转录因子在ChIP-seq中独有的Peak则暗示这个转录因子可能是结合在异染色质区域的驱动型转录因子(Pioneer TFs)。对于组蛋白修饰的ChIP-seq而言,前面也说过组蛋白乙酰化与染色质开放区形成有重要联系,同样可以与ATAC-seq进行联合分析。 RNA-seq:比如将ATAC-seq的信号在gene body上的分布做比较,或者按照基因不同的表达量做分类,再与ATAC-seq联用分别统计不同表达量基因的TSS上的富集信号,研究ATAC-seq的富集信号是否与基因表达量相关、差异表达的基因是否受染色质可及性的调控等等。 2. CUT&TagCUT&Tag全称Cleavage Under Target & Tagmentation,翻译为靶向剪切及转座酶技术,是一种研究蛋白-DNA互作的技术,替代传统的ChIP-seq方法。开头介绍ATAC-seq和CUT&Tag非常相似,原因就在于CUT&Tag也用Tn5转座酶,只不过这个酶是Protein A/G融合的Tn5转座酶。当然,两者研究内容还是不一样的,下面详细说一下。 2.1 背景介绍ChIP前面介绍ATAC-seq的时候已经拿ChIP-seq做过对比,这里为了引出CUT&Tag还是对ChIP技术做个简单介绍。ChIP全称Chromatin Immunoprecipitation,翻译为染色质免疫共沉淀,顾名思义,这个技术有个鲜明的特征就是抗原抗体免疫反应。 下面是ChIP-seq的经典流程: 我们知道,抗原抗体反应具有专一性,所以我们制备特定的转录因子的抗体(一般是多克隆抗体),将细胞核内的染色质用甲醛交联固定后进行超声破碎,这个时候与转录因子结合的DNA序列不会被打断。加入抗体做免疫共沉淀,解交联后获得与转录因子结合的DNA序列,建库测序就可以做后面的分析。 ChIP技术分类 N-ChIP:用的是Native Chromatin,原生态染色质,DNA片段化的方法为酶切,用上面提到的MNase。只能处理结合能力比较强的蛋白(一般用于组蛋白修饰),蛋白复合体存在解离的风险,实验难度相对较大。 X-ChIP:用的是Cross-linked Chromatin,甲醛交联的染色质,DNA片段化方式为超声波。甲醛交联的背景一般比较高,抗体识别位点可能会被屏蔽。 ChIP-seq技术非常依赖于抗体质量,如果研究低表达的蛋白,制备抗体的也是很大的挑战。近年在鉴定转录因子结合位点上又出了个新技术DAP-Seq,通过体外蛋白表达技术,表达出带有Halo标签的转录因子蛋白。通过Halo标签的抗体富集对应的蛋白DNA复合物,从而使所有蛋白都可以被一种抗体(Halo标签抗体)富集,绕过了抗体制备的难题(和后面要说的没啥关系,写到技术分类这里提一嘴)。 CUT系列技术 CUT&RUN:Cleavage Under Targets and Release Using Nuclease,靶向剪切和核酸酶释放,这里用的核酸酶是与Protein A/G融合的MNase。 CUT&Tag:Cleavage Under Targets and Tagmentation,靶向剪切和转座酶,这里用的酶是Protein A/G融合的Tn5转座酶,是CUT&RUN技术的改进版。 两种技术用的酶不一样,Tn5转座酶可以插入测序标签,因此CUT&Tag比CUT&RUN节省了更多建库时间,1天就可以完成建库。 2.2 实验流程整个实验流程可以分为6步: 收集细胞,刀豆蛋白的磁珠吸附细胞膜 细胞电穿孔后分别孵一抗和二抗,一抗结合目标蛋白,二抗放大信号 Hyperactive pA/pG-Tn5 Transposon结合,这种改造的Tn5转座酶会特异性识别抗体并结合 加入镁离子或者钙离子激活Tn5转座酶,片段化DNA 提取DNA PCR扩增文库 上面的流程图可以看出,CUT&Tag与ATAC-seq的区别就在于,CUT&Tag用了抗体,Tn5转座酶进一步改造可以识别抗体。 因此CUT&Tag实验需要做阴性对照(IgG),CUT&Tag文库和ATCA文库差别就在于抗体识别的蛋白,如果抗体的特异性很差,比如在所有组蛋白上都有结合,那这个时候建库得到的就是ATCA文库而不是CUT&Tag文库。这个时候阴性对照就很重要了,如果在IgG上也能出正常的PCR结果,就说明抗体有问题或者实验设计有问题了。 因为CUT&Tag用的是Tn5转座酶,所以也有一个缺陷,比如要研究异染色质的蛋白与DNA互作就做不了,因为Tn5转座酶无法在异染色质区剪切DNA。或者你要研究的蛋白空间结构比较大,导致Tn5转座酶无法剪切到DNA,这个时候也不能用CUT&Tag。 2.3 生信分析CUT&Tag生信分析和ATAC-seq几乎一模一样,下游分析也可以做IDR Peak、Peak Annotation和Motif Analysis,这里就不赘述了。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"表观遗传学","slug":"表观遗传学","permalink":"http://www.shelven.com/categories/%E8%A1%A8%E8%A7%82%E9%81%97%E4%BC%A0%E5%AD%A6/"}],"tags":[{"name":"ATAC-seq","slug":"ATAC-seq","permalink":"http://www.shelven.com/tags/ATAC-seq/"},{"name":"CUT&Tag","slug":"CUT-Tag","permalink":"http://www.shelven.com/tags/CUT-Tag/"}]},{"title":"go-cqhttp登录异常(错误码45)的解决办法","slug":"go-cqhttp登录异常(错误码45)的解决办法","date":"2023-08-04T14:57:57.000Z","updated":"2023-08-04T15:01:30.000Z","comments":true,"path":"2023/08/04/a.html","link":"","permalink":"http://www.shelven.com/2023/08/04/a.html","excerpt":"曾经写了一篇go-cqhttp扫码登录异常的解决方法点击此处,当时go-cqhttp版本为v1.0.0,如今(2023年8月4日)已不再适用。在v1.1.0版本之后需要自建或者使用别人搭建的签名服务器,否则会返回错误代码45,这里记录下自己的操作和踩的坑。","text":"曾经写了一篇go-cqhttp扫码登录异常的解决方法点击此处,当时go-cqhttp版本为v1.0.0,如今(2023年8月4日)已不再适用。在v1.1.0版本之后需要自建或者使用别人搭建的签名服务器,否则会返回错误代码45,这里记录下自己的操作和踩的坑。 问题描述go-cqhttp更新版本v1.1.0之后,无法通过替换device.json文件登录,返回错误码45,需要配置签名服务器。 解决方法1. linux操作系统解决方法1.1 安装和启动docker123456789# 下载docker安装脚本,一键安装curl -fsSL https://get.docker.com -o get-docker.shbash get-docker.sh# 启动docker服务systemctl start docker# 查看docker状态,是否正常启动service docker status 1.2 查看ANDROID_ID如果你的device.json文件还没有删除,打开它,找到android_id值,比如我这里是d9b3a88f3cd1f951(瞎打的,总之是这样子的格式)。 如果你已经删除上面的文件,再次运行go-cqhttp,会自动生成device.json文件,同上操作。 这个参数非常重要,每次重启go-cqhttp会随机生成新的android_id值,如果使用别人的签名服务器,需要将你的device.json文件中android_id值改成别人签名服务器提供的,两者的值要对应。 1.3 自建签名服务器实例化qsign的docker镜像: 12345678910# 开放防火墙端口(用docker默认的8080)firewall-cmd --permanent --add-port=8080/tcp# 重载防火墙firewall-cmd --reload# 运行dockerdocker run -d --restart=always --name qsign -p 8080:8080 -e ANDROID_ID=d9b3a88f3cd1f951 xzhouqd/qsign:8.9.63# 检查运行是否成功,访问主机端口没有报错就行curl localhost:8080 映射的主机端口不要改,就用默认的8080,否则docker运行后会无法访问映射的主机端口,curl l映射的端口会出现报错curl: (56) Recv failure: Connection reset by peer,运行go-cqhttp也会出现获取协议T544报错和获取sso sign报错reset by peer! 1.4 修改config.yml回到go-cqhttp目录,修改config.yml文件的sign-server字段,如下: 如果没有该字段,说明你的go-cqhttp版本不对,去github下载新的。 1.5 运行go-cqhttp如果你之前用过go-cqhttp,把主目录下的data/versions/6.json文件删除,否则会因为协议版本和签名服务器的协议版本不一致导致仍然报错45,仍然提示你配置sign service! 删除6.json后,同样会让你进行滑块验证,到上面提示的网址手动滑块验证后,可能和我一样会提示账号开启了设备锁,不要慌,同样是到提示的网站,用手机QQ扫一下(不需要服务器和手机QQ在同一个网络环境),然后再次运行go-cqhttp,再次进行滑块验证即可。 成功运行~ 2.windows操作系统解决方法2.1 查看ANDROID_ID这步和1.2一样,先从device.json获取android_id值,不再赘述。 2.2 自建签名服务器下载这个仓库Releases · fuqiuluo/unidbg-fetch-qsign (github.com),release版本V1.0.3 JAR,注意版本。 解压后可以看到两个目录bin和lib,在主目录下创建txlib/8.9.86这样结构的两个文件夹,将unidbg-fetch-qsign/txlib/8.9.63 at master · fuqiuluo/unidbg-fetch-qsign (github.com)这个仓库下的两个.so结尾的文件下载到8.9.86文件夹中。 回到主目录,创建一个批处理文件run.bat: 123.\\bin\\unidbg-fetch-qsign.bat --host=127.0.0.1 --port=8080 --count=1 --library=txlib\\8.9.63 --android_id=d890fd5ae11be94d# 这里--port可以自定义端口,--count表示最大连接数,--library就是两个.so文件的路径,--android_id是第一步你获取的值 双击run.bat,一会儿后看到[FEKit_]info: task_handle.h:74 TaskSystem not allow是正常的。 2.3 修改config.yml,运行go-cqhttp同1.4,1.5,不再赘述。 以上实现方式均参考自签名服务器相关问题 · Mrs4s/go-cqhttp · Discussion #2245 (github.com) 我自己在linux和windows上均试过没有问题,感谢提出解决方案的大佬。","categories":[{"name":"QQ机器人","slug":"QQ机器人","permalink":"http://www.shelven.com/categories/QQ%E6%9C%BA%E5%99%A8%E4%BA%BA/"}],"tags":[{"name":"qq bot","slug":"qq-bot","permalink":"http://www.shelven.com/tags/qq-bot/"},{"name":"go-cqhttp","slug":"go-cqhttp","permalink":"http://www.shelven.com/tags/go-cqhttp/"}]},{"title":"Hi-C染色体挂载(2)——3D-DNA配置运行和手动调整Hi-C互作图","slug":"Hi-C染色体挂载(2)——3D-DNA配置运行和手动调整Hi-C互作图","date":"2023-07-13T13:47:54.000Z","updated":"2023-07-17T13:19:41.000Z","comments":true,"path":"2023/07/13/a.html","link":"","permalink":"http://www.shelven.com/2023/07/13/a.html","excerpt":"前一篇博客主要讲了如何使用juicer进行Hi-C测序的下机数据处理,这篇博客我们按照Aiden团队的基因组组装“CookBook”继续接下来的操作,主要记录下3D-DNA软件的配置运行,以及如何手动调整结果。","text":"前一篇博客主要讲了如何使用juicer进行Hi-C测序的下机数据处理,这篇博客我们按照Aiden团队的基因组组装“CookBook”继续接下来的操作,主要记录下3D-DNA软件的配置运行,以及如何手动调整结果。 1. 下载和配置3D-DNA123456789101112131415# 拉取3D-DNA仓库git clone https://ghproxy.com/https://github.com/aidenlab/3d-dna.git## 将run-asm-pipeline-post-review.sh和run-asm-pipeline.sh属性分别改成可执行,对应chmod的0755权限chmod 755 run-asm-pipeline-post-review.shchmod 755 run-asm-pipeline.sh# 安装LastZ(跑二倍体模式需要用,一个DNA序列比对工具)conda install LastZ# 安装GNU Parallel## 官方推荐用该shell下实现并行运算的工具,加快程序运行速度。集群用户如果没有预装可以在~/bin中以如下方式编译安装wget http://ftp.gnu.org/gnu/parallel/parallel-latest.tar.bz2tar -xvjf parallel-latest.tar.bz2cd parallel-20230622./configure --prefix=$HOME && make && make install 3D-DNA有两个pipeline脚本,12个独立的功能模块(每个模块一个文件夹),官方给出的pipeline如下: 三个橘色框是需要手动调整的地方,稍微了解下这几个独立模块分别在做什么(具体参考manual_180319 (aidenlab.org)): 主要的8个独立模块(对应上面的流程): scaffold:对给定的scaffolds进行排序和定向,转换基于3D proximity的一系列单个片段序列成为单个的megascaffold,这个megascaffold用于检测错误的连接或者保留用于后续处理(split模块还会用到) visualize:与后续的Juicebox Assembly Tools处理对接,也就是可视化组装结果。在3D-DNA中用于连接各个处理阶段,便于审查和调整参数。 edit:包含检测3D-DNA错误连接的脚本和一些校正算法,这个模块在做特殊处理的时候最可能需要调整参数 polish:校正3D-DNA scaffolding算法可能引起的错误,类型基因组的polish过程 split:分割megascaffold成为染色体 seal:消除上一步结果的假阳性(通过引入碎片的方式,详细看手册说明) merge:融合代表替代单倍型的组装片段(仅在二倍体模式下)起作用 finalize:生成最终的fasta格式输出,在最终fasta生成过程中,考虑到可能的顺序和方向,将被识别为同一染色体内部的scaffold连接起来,同时在每个scaffold之间添加固定大小(500bp)的间隙 附加的4个独立模块: utils:一些其他模块用到的核心脚本,包括从.fasta文件生成.assembly文件 lift:一些从基因组草图到最后组装结果的核心脚本 supp:几个附加脚本,包括生成色谱图的脚本 data:一些数据表 如果要研究各个步骤的算法就可以在各个模块中找源码,现在的我只是个调包侠,程序不出问题就不深入研究了(以后有时间一定= =) 两个pipeline脚本也是对应不同阶段使用的: run-asm-pipeline.sh 将几个独立模块串联,组装基因组(也就是上面流程图用的脚本) run-asm-pipeline-post-review.sh 用于Juicebox Assembly Tools (JBAT)手动调整之后执行,只会执行上个脚本的后几个阶段(finalize或者seal和finalize),生成最终的hic文件(用于质控)和fasta序列 2. 运行3D-DNA了解不同脚本和模块作用后,接下来就是很简单的设置脚本参数运行了。 123456789101112131415161718# 创建run.slurm脚本vim run.slurm# 脚本内容如下---------------------------------------------------------------#!/bin/bash#SBATCH -n 30#SBATCH -N 1#SBATCH -t 7200/public/home/wlxie/biosoft/3d-dna/run-asm-pipeline.sh \\ /public/home/wlxie/biosoft/juicer/reference/genome.fa \\ -r 0 \\ /public/home/wlxie/biosoft/juicer/aligned/merged_nodups.txt---------------------------------------------------------------# 运行脚本sbatch run.slurm 220Mb参考基因组,50G的merged_nodups.txt文件,实际运行结束时长为10小时。 run-asm-pipeline.sh脚本用法和参数如下: 12345678910USAGE: ./run-asm-pipeline.sh [options] <path_to_input_fasta> <path_to_input_mnd>path_to_input_fasta:组装的基因组fasta文件位置path_to_input_mnd:juicer处理后得到的去重复的Hi-C reads比对基因组文件位置,也就是merged_nodups.txtOPTIONS:-m|--mode haploid/diploid 单倍体/二倍体模式(默认单倍体)-i|--input input_size 指定输入的 contig/scaffold size阈值,默认15000,小于15000的将被忽略-r|--rounds number_of_edit_rounds 指定纠错轮数(默认为2轮)-s|--stage stage 指定运行的阶段,可以是polish, split, seal, merge和finalize 对于运行模式,作者团队建议是先运行默认的haploid模式,检查拼接后的图谱中是否存在与杂合度不足相关的信号,diploid模式会尝试合并一些单倍型。 The diploid mode is to be used when one suspects large amounts of under collapsed heterozygosity to be present in the draft genome assembly (this was indeed the case for AaegL2). Running in the diploid mode will attempt to analyze the final assembly to remove the interspersed haplotigs and merge overlaps not recognized by the contigger because of variant differences. In most cases the default haploid mode should be used. 可以看官方的这篇文章帮助理解下两种模式的区别,以及软件LastZ在diploid模式中起的作用:Extended_Hi-C_methods.docx (dropbox.com) 至于纠错轮数,最好是按照默认的参数跑2轮,然后选择效果最好的一次结果导入juicerbox进行调整。 那么问题来了,怎么确认用哪个结果呢?首先要确认下结果文件有些啥。 3. 3D-DNA结果文件这个软件产生的结果文件非常多,中间步骤产生的文件是不会自动删除的,看一下主要的几个文件: .fasta 基因组序列文件,最终组装的基因组序列名称为.FINAL.fasta .hic 高度压缩的二进制文件,与下面的.assembly文件都可以载入Juicebox Assembly Tools,不同阶段产生不同的hic文件,0表示未校正 .assembly 空格分隔的文本,记录对基因组草图执行的指令,包括拆分、更改顺序、方向和锚定到染色体中(不同阶段都会产生,同时产生同名的.cprops和.asm,这两种文件已弃用) .scaffold_track.txt & .superscaf_track.txt scaffold和super scaffold的染色体边界文件,Juicebox 2D 注释格式,使用juicerbox的时候可以用到,但是使用Juicebox Assembly Tools不需要用(因为边界注释会根据. assembly 文件自动生成) .bed & .wig 对pipeline进行故障审查的时候用到,可以与.hic文件导入juicebox,暂时与Juicebox Assembly Tools不兼容 可以看出很多中间文件还是为了导入juicebox做分析产生的,而我们仅仅是为了辅助基因组组装,确认染色体的挂载情况,用到的是Aiden团队开发的juicebox的桌面应用拓展模块,也就是Juicebox Assembly Tools(JBAT),很多中间文件已经用不到了,这里我们需要用到的是.hic和.assembly两种类型文件,且这两种文件名称是一一对应的。 如果是默认参数跑的话还会有genome.1.hic和genome.2.hic两个文件,分别代表校正了一次和两次的结果,都可以导入JBAT看结果。 看很多教程其实陷入了误区,需要校正多少轮,用哪一组数据其实没有固定说法,有的人用genome.0.hic,有的人用genome.1.hic,都是可以的,这几个只是polish前和polish后产生的hic文件,别忘了我们还有其他阶段产生的hic文件,都是可以用的。这里我用genome.rawchrom.hic和对应的genome.rawchrom.assembly这组文件,因为这组文件实际上区分染色体的情况最好。 下一步就是导入JBAT手动调整染色体边界和修改组装过程中产生的错误。 4. 导入JBAT这一步在个人电脑上完成,最好保证电脑有4G以上的运存(2023年了个人电脑都有这个配置了吧),否则会巨卡无比。 我用的官方1.11.08版本的windows工具: https://s3.amazonaws.com/hicfiles.tc4ga.com/public/Juicebox/Juicebox_1.11.08.exe 不要安装直接可以运行,首先导入.hic文件: 导入hic文件之后Assembly菜单就可以点了,导入对应名字的.assembly文件: 完整导入后界面如图所示,主要是用到以下几个菜单栏: ①:标准化方式,选择balance看起来舒服点= = ②:分辨率,调整显示的分表率大小 ③:色彩范围,调整表示互作强度的色彩范围 ④右下角几个颜色块代表图上线条框的区域处于什么状态。默认黄色线框的是待编辑区(这里没有,对区块进行操作的时候才有),紫色线框是染色体区块,绿色线框是scaffold区域 5. 调整Hi-C互作图我这个互作图没有明显的需要染色体易位、倒位的调整,只需要确定染色体边界(也就是蓝色的线)就行。 染色体易位和倒位的调整在官方视频里就有,b站有人做了翻译,看一个视频足够了翻译 | Juicebox Assembly Tools教程_哔哩哔哩_bilibili 调高分辨率,比如下面我要做的就是将紫色框分成两个: 放大到一定分辨率后,scaffold的交界处鼠标会变成L型,点一下就可以加上染色体边界,这里截图没截出来…… 还有一种方法,长按shift键,单击拖动鼠标框选住你要操作的scaffold(不需要完全覆盖scaffold,只要框的面积里有你要的scaffold就行),松开shift键,这个时候选中的scaffold会处于待编辑状态(黄色的框线)。右键,选择Add chr boundaries即可为选中的scaffold区域添加染色体边界。 如果想要撤销操作,右键后点击Undo即可。 整体调整完以后是这样: 染色体条数不是无缘无故添加的,既要以图为准,又要由实验确定,或者通过已经发表的文献(该物种或者近缘物种)佐证,纠正算法产生染色体边界的误差。我是通过查阅文献得到这个物种染色体条数为11,调整后的Hi-C互作图也能明显分为11个互作区块,证实组装是没有问题的。 官方的操作手册上也有详尽的手动调整Hi-C互作图教程,可以参考manual_180322.pdf (dropbox.com)。 手动调整完后,导出调整后的.assembly文件,我们需要用这个文件重新回到3D-DNA生成最终的染色体水平基因组fasta文件。 6. 3D-DNA处理简单来说,我们在JBAT导出的最终.assembly文件记录了各个scaffold的坐标位置和互作信息,只需要在3D-DNA中跑最后finalize流程就可以了,软件提供了脚本run-asm-pipeline-post-review.sh: 12345678910111213USAGE: ./run-asm-pipeline-post-review.sh [options] -r <review.assembly> <path_to_input_fasta> <path_to_input_mnd> path_to_input_fasta:指定组装的草图基因组fasta文件path_to_input_mnd:指定juicer产生的merged_nodups.txtOPTIONS:-r|--review path_to_review_assembly 指定上一步JBAT导出的.assembly文件-i|--input input_size 指定输入的 contig/scaffold size阈值,默认15000,小于15000的将被忽略-s|--stage stage 指定运行开始的阶段,可以是seal或者finalize,默认finalize-q|--mapq mapq 指定最终map的可视化MAPQ阈值,默认1-g|--gap_size gap_size 指定最终scaffold之间的间隔,默认500--sort-output 对最终输出的染色体级别的scaffold长度排序,降序--build-gapped-map Option to output an additional contact map corresponding to the assembly after the gaps have been added between scaffolded sequences.(我也不知道干啥的) 比较重要的就三个文件位置参数: 123456789101112131415161718# 创建run_postview.slurm脚本vim run_postview.slurm# 脚本内容如下---------------------------------------------------------------#!/bin/bash#SBATCH -n 30#SBATCH -N 1#SBATCH -t 7200/public/home/wlxie/biosoft/3d-dna/run-asm-pipeline-post-review.sh \\ -r /public/home/wlxie/biosoft/3d-dna/baima_diploid/genome_rawchrom.review.assembly \\ /public/home/wlxie/biosoft/juicer/reference/genome.fa \\ /public/home/wlxie/biosoft/juicer/aligned/merged_nodups.txt---------------------------------------------------------------# 运行脚本sbatch run_postview.slurm 静静等待生成最终的fasta序列就可以啦,如果前面做过contig基因组草图注释的话,还需要将注释的基因信息转坐标到最终的染色体水平基因组上;如果没做过注释的话,跑一遍注释流程吧! 2023.7.17补充最终生成的基因组序列要看genome.FINAL.fasta,发现染色体数目和前面手动标定染色体边界的数目不一致,这很正常。可以用seqkit工具统计下各个染色体的长度,如下: 可以看到前11条是染色体级别的contig,这和手动标记的染色体数目一致,后面的contig长度占比非常小,可以标记区分一下,这些都要放在组装的基因组文件里。 顺便放一下自己写的python代码,同样的处理效果,顺手就当python练习了(运行前提:fasta文件中没有空行)。 123456789101112131415161718192021fasta_file = 'genome.FINAL.fasta' # 换成自己的fasta文件路径def fasta_length(file_path): sequences = {} with open(file_path, 'r') as file: content = file.read() # 读整个文件 blocks = content.split('>') # 根据关键字“>”分块 for block in blocks[1:]: # 第一个块为空,跳过 index = block.find('\\n') # 查找第一个换行符索引 newline_number = block.count('\\n') # 统计换行符数量 header = block[:index] # 序列名称 sequence_length = len(block) - index - newline_number # 序列长度 sequences[header] = sequence_length # 序列名称和长度存入字典 return sequencessequence_lengths = fasta_length(fasta_file)for header, length in sequence_lengths.items(): print(f'{header}:{length}')","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"Hi-C染色体挂载","slug":"Hi-C染色体挂载","permalink":"http://www.shelven.com/tags/Hi-C%E6%9F%93%E8%89%B2%E4%BD%93%E6%8C%82%E8%BD%BD/"},{"name":"3D-DNA","slug":"3D-DNA","permalink":"http://www.shelven.com/tags/3D-DNA/"},{"name":"JBAT","slug":"JBAT","permalink":"http://www.shelven.com/tags/JBAT/"}]},{"title":"Hi-C染色体挂载(1)——juicer2处理Hi-C数据","slug":"Hi-C染色体挂载(1)——juicer2处理Hi-C数据","date":"2023-07-11T10:04:56.000Z","updated":"2023-07-11T13:03:50.000Z","comments":true,"path":"2023/07/11/a.html","link":"","permalink":"http://www.shelven.com/2023/07/11/a.html","excerpt":"前面经过三代数据结合二代数据的组装和polish,已经把基因组组装成了contigs的水平,下一步就是进一步提升到染色体水平。从实现的方式上来说有Bionano的光学图谱技术(作用是减少Scaffold数量,基因组纠错),Hi-C技术,遗传图谱以及依靠算法实现的基于近缘物种参考基因组的染色体水平组装(比如RagTag)。","text":"前面经过三代数据结合二代数据的组装和polish,已经把基因组组装成了contigs的水平,下一步就是进一步提升到染色体水平。从实现的方式上来说有Bionano的光学图谱技术(作用是减少Scaffold数量,基因组纠错),Hi-C技术,遗传图谱以及依靠算法实现的基于近缘物种参考基因组的染色体水平组装(比如RagTag)。 对于一个没有遗传图谱,也没有近缘物种参考基因组的物种,考虑到光学图谱费用昂贵,一般最划算用的最多的是测一个Hi-C来进行基因组染色体级别组装。一些经典的Hi-C辅助组装的软件应运而生,比如LACHESIS、3D-DNA、YaHS等等。这篇笔记主要记录下软件3D-DNA的染色体挂载流程。 然而3D-DNA不能直接处理Hi-C下机数据,因此总的流程是用juicer2先处理Hi-C数据,再用3D-DNA进行染色体挂载,最后用Juicebox Assembly Tools (JBAT)进行手工纠错。流程参考自Baylor College of Medicine & Rice University Aiden团队Genome Assembly Cookbook,这几个软件也是出自他们团队。 juicer2Juicer是一款非常经典的Hi-C数据处理软件,但是配置运行起来稍微有点麻烦,花了小半天时间踩坑记录一下。需要非常注意的一点,直接从github官网拉取的juicer仓库是juicer2,在release版本中有一个稳定版juicer1.6,两者的配置和产生的结果文件是不一样的!!! 秉着软件用新不用旧的原则,本篇笔记全程用的是juicer2,如果有软件运行问题可以在3D Genomics - Google 网上论坛交流,或者在github官方提Issues(不建议)。 顺便提一下我使用Juicer2过程中碰到的版本问题: juicer2配置的juicer_tools版本要在2.0以上,否则会报错Exception in thread "main" java.lang.RuntimeException: Unknown command: statistics,该报错会直接导致无法生成inter.txt、inter_hists.m 、inter_30.txt和inter_30_hists.m文件,也就是Hi-C互作的统计数据和矩阵。 juicer2结果文件中不会自动产生merged_nodups.txt文件,该文件原本作为3D-DNA的输入文件,在juicer2中被同名的merged_nodups.bam文件和merged*.txt代替。如果想要merged_nodups.txt文件,需要加上参数--assembly。 1. 下载和配置juice212345678910111213# 拉取最新版本的juicer2仓库(这里用的github镜像,集群可能有dns污染无法直接访问github)git clone https://ghproxy.com/https://github.com/aidenlab/juicer.gitcd juicer# juicer主目录下配置scripts(必需)## 个人电脑的是建立软链接到CPU文件夹下,其他如slurm、LSF、AWS和Univa等集群或者云端跑juicer是建立软链接或者复制到对应文件夹的scripts文件夹下ln -s CPU scripts# scripts/common文件夹中下载juicer_tools(必需)cd scripts/commonwget -c https://ghproxy.com/https://github.com/aidenlab/Juicebox/releases/download/v2.20.00/juicer_tools.2.20.00.jar# 修改文件属性为可执行文件后,建立软链接ln -s juicer_tools.2.20.00.jar juicer_tools.jar 本来想配置slurm跑的,因为塔大集群使用的是slurm作业调度系统。花了好大功夫配置环境发现运行SLURM/scripts底下的juicer.sh还需要配置CUDA,晕,学校的集群没有GPU(怨念 = =)……所以如果集群没有GPU的话就老老实实用CPU文件夹下的juicer.sh,按照CPU流程跑后面的程序,缺点是不能用集群提交多线程作业非常难受。 官方也是推荐小的Hi-C实验可以用CPU版本,如果数据量比较大,还是推荐带有GPU加速的集群(最好是SLURM)或者云端跑。如下是官方推荐的两种方式: Running Juicer on a cluster · aidenlab/juicer Wiki (github.com) ENCODE-DCC/hic-pipeline: HiC uniform processing pipeline (github.com) juicer主目录下需要配置两个必需的文件夹reference和restriction_sites(第二个在juicer流程中非必须,但是做染色体挂载一定要) 12345678910# juicer目录下创建参考基因组文件夹,放入参考基因组(复制或者软链接)和构建参考基因组索引文件(必需用bwa)mkdir reference && cd referencecp /path/to/your/reference/genome.fa ./bwa index genome.fa# juicer目录下创建限制性酶切位点文件夹,MboI酶酶切参考基因组(根据自己测hic用的限制酶)mkdir restriction_sites && cd restriction_sitespython ~/biosoft/juicer/misc/generate_site_positions.py MboI genome ~/biosoft/juicer/reference/genome.fa## 生成的酶切图谱文件名为genome_MboI.txt,同个文件夹下生成contig长度文件awk 'BEGIN{OFS="\\t"}{print $1, $NF}' genome_MboI.txt > genome.chrom.sizes 官方的酶切参考基因组的脚本generate_site_positions.py支持HindIII、DpnII、MboI、Sau3AI和Arima4种限制性核酸内切酶,如果你Hi-C测序用的是其他酶,可以根据实际修改这个脚本中的字典类型数据patterns,根据实际情况加入键值对信息: 稍微解释一下官网给的awk 'BEGIN{OFS="\\t"}{print $1, $NF}'这个命令,这里awk的程序部分由两部分组成: BEGIN{OFS="\\t"}:BEGIN块是在处理文件之前执行的代码块。在这里,它设置输出字段分隔符(OFS)为制表符(\\t)。这意味着输出的字段将使用制表符进行分隔。 {print $1, $NF}:这是awk的主要部分,它定义了要执行的操作。在这里,它打印每行的第一个字段$1和最后一个字段(NF)。通过使用逗号分隔它们,它们将以制表符分隔的形式打印出来。 genome.chrom.sizes文件如下所示: 工作目录下必需配置一个文件夹fastq,其中存放Hi-C的双端测序数据,命名格式参考juicer.sh文件中的正则表达式*_R*.fastq*。 1234# 工作目录下创建存放hic测序数据的文件夹,我这里为了方便查看,工作目录就是juicer主目录,测序数据可以用软链接的形式存放mkdir fastq && cd fastqln -s ~/hi-c/BM_R1.fq.gz Av_R1.fastq.gzln -s ~/hi-c/BM_R2.fq.gz Av_R2.fastq.gz 2. 运行juicer2官网的juicer.sh参数非常之多,我这里只展示一些关键的,其他用默认参数即可。1.x版本可以参考Usage · aidenlab/juicer Wiki (github.com),2.0版本具体可以用bash juicer.sh -h查看,两个版本指令稍有不同。CPU版本和其他集群版本也稍有不同,以CPU中的2.0版本为例: 12345678910111213141516171819202122232425262728Usage: juicer.sh [-g genomeID] [-d topDir] [-s site] [-a about] [-S stage] [-p chrom.sizes path] [-y restriction site file] [-z reference genome file] [-D Juicer scripts parent dir] [-b ligation] [-t threads] [-T threadsHic] [-i sample] [-k library] [-w wobble] [-e] [-h] [-f] [-j] [-u] [-m] [--assembly] [--cleanup] [--qc]-g genomeID 必需项,指定基因组和版本,比如人类的"hg19" 或者小鼠的"mm10",如果没有,可以用-z命令替代,指定参考基因组文件-z reference genome file 指定参考基因组文件,此文件中必需有BWA索引文件-d topDir 指定输出目录,默认是当前目录。[topDir]/fastq必需包含fastq文件-s site 必需项,指定限制性核酸内切酶。如果不做片段级别的分析可以写none-S stage 指定运行pipeline的阶段,固定是"chimeric", "merge", "dedup", "final", "postproc", "early"其中之一,报错调试用 chimeric: alignment结束或者从aligned文件开始 merge: alignment结束但是没有生成merged_sort文件 dedup: 文件已经merge结束,但是没有生成merged_nodups文件 final: reads在merged_nodups文件中已经删除重复,但是尚未生成最终的state和hic文件 postproc: hic文件已经生成,只有特征注释尚未完成 early: 在hic文件生成前提前结束程序-p chrom.sizes path 指定染色体长度文件-y restriction site file 指定参考基因组酶切文件-D Juicer scripts parent dir 指定Juicer/scripts所在目录-t threads 指定跑BWA的线程数-T threadsHic 指定生成hic文件用的核数(2.0版本新增)--assembly 接下游3D-DNA分析,会提前结束并生成老版本的merged_nodups文件(2.0新增)--cleanup 如果pipeline成功运行,自动清除中间文件(2.0新增)如果是在集群中跑juicer,以下两个参数也是必需要改的,否则用默认分区名会直接报错:-q queue 指定跑alignments的队列分区(默认commons),slurm中的分区也就是partition,可以理解为LSF、PBS等作业调度系统中的队列-l long queue 指定跑需要时间更长的job,比如生成hic文件的队列分区(默认long) 因为我不是做三维基因组,Hi-C只测了一个样(主要用于辅助基因组组装,染色体挂载),比如做三维基因组就会有大量的Hi-C数据,juicer也支持多个结果的合并(使用mega.sh),详细也可以参考上面的juicer Wiki。 上面的限速步骤主要是跑BWA,因为第一次跑juicer不知道要多久,就稍微申请多一点的核数(虽然集群没有GPU,但是CPU资源很充足平常没人用)。 12345# 创建一个slurm作业vim juicer.slurm# 提交slurm作业sbatch juicer.slurm juicer.slurm文件内容如下(再次提醒--assembly 参数非常重要): 12345678910111213#!/bin/bash#SBATCH -N 1#SBATCH -n 30#SBATCH -t 7200/public/home/wlxie/biosoft/juicer/scripts/juicer.sh \\-z /public/home/wlxie/biosoft/juicer/reference/genome.fa \\-p /public/home/wlxie/biosoft/juicer/restriction_sites/genome.chrom.sizes \\-y /public/home/wlxie/biosoft/juicer/restriction_sites/genome_MboI.txt \\-s MboI \\-D /public/home/wlxie/biosoft/juicer \\-t 30 \\--assembly 运行时间可以参考一下我这里220 Mb的参考基因组,Hi-C测序数据39G(双端测序,gz文件),实际上运行了13小时才出结果。 运行pipeline成功后会在日志中提示(-: Pipeline successfully completed (-: 3. 结果文件CPU模式下会在工作目录创建aligned和splits文件夹: splits 文件夹存放整个pipeline的临时文件,跑完流程并且确认结果没问题后可以运行cleanup.sh或者直接删除(按照帮助文档的说法,–cleanup参数应该可以自动删除,我没有试过) aligned 文件夹存放运行结果 如果你是用集群模式跑的,比如SLURM,还会生成一个debug文件夹(CPU模式下用SLURM跑的没有该文件夹),可以查看各个步骤的error报告和output报告 aligned文件夹有以下结果文件: 我这里实际上是多了一些文件(实际上跑了两遍),因为一开始没有加上--assembly参数,所以一直运行到出hic结果文件,而且没有merged_nodups.txt。 我做染色体挂载只需要最后一个文件,第二次运行加了上面的参数,并且加上-S final参数,这样就省去了前面比对和去重复的时间(90%以上的时间花在这里),一个小时可以重新跑完出结果。 顺便了解一下其他结果文件的用途: inter.hic / inter_30.hic: The .hic files for Hi-C contacts at MAPQ > 0 and at MAPQ >= 30, respectively merged_nodups.txt: The Hi-C contacts with duplicates removed. This file is also input to the assembly and diploid pipelines (后面跑3D-DNA的输入文件) inter.txt, inter_hists.m / inter_30.txt, inter_30_hists.m: The statistics and graphs files for Hi-C contacts at MAPQ > 0 and at MAPQ >= 30, respectively. These are also stored within the respective .hic files in the header. The .m files can be loaded into Matlab. The statistics and graphs are displayed under Dataset Metrics when loaded into Juicebox (可以后续导入juicer box)","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"juicer2","slug":"juicer2","permalink":"http://www.shelven.com/tags/juicer2/"},{"name":"Hi-C染色体挂载","slug":"Hi-C染色体挂载","permalink":"http://www.shelven.com/tags/Hi-C%E6%9F%93%E8%89%B2%E4%BD%93%E6%8C%82%E8%BD%BD/"}]},{"title":"python自学笔记(8)——10种排序方式的python实现","slug":"python自学笔记(8)——10种排序方式的python实现","date":"2023-07-04T15:14:44.000Z","updated":"2023-07-04T15:37:54.000Z","comments":true,"path":"2023/07/04/a.html","link":"","permalink":"http://www.shelven.com/2023/07/04/a.html","excerpt":"最近中期答辩结束,稍微有点空闲的时间捋一捋数据结构和算法方面的知识。虽然现在用python实现排序就一个sort()函数的事,但是还是想锻炼下自己的思维,从底层代码学习一下10种经典排序的实现方式。","text":"最近中期答辩结束,稍微有点空闲的时间捋一捋数据结构和算法方面的知识。虽然现在用python实现排序就一个sort()函数的事,但是还是想锻炼下自己的思维,从底层代码学习一下10种经典排序的实现方式。 对于时间复杂度和空间复杂度的计算,自己还是一知半解,每种排序方式后面放了自己的理解,有错误会继续修改,最后有一张菜鸟教程总结的图可以参考。 本篇笔记的内容主要是跟着b站up主做的,代码整理来自英雄哪里出来的个人空间 我这里先定义一个生成随机整数序列的函数,后面都会调用,就不重复写了: 12345678import random# 生成指定范围、长度的序列def generate_random_sequence(len, min, max): seq = [] for i in range(len): seq.append(random.randint(min, max)) return seq 1. 选择排序从第一个元素到最后一个元素中选择最小的元素,和第一个元素进行交换;然后从第二个元素到最后一个元素选择最小的元素,和第二个元素交换,依此类推。通过对未排序的元素比较和交换,选择出最小的,直到最后成为一个升序序列。 1234567891011121314151617181920212223242526def SelectionSort(a): n = len(a) for i in range(n - 1): # 排完倒数第二个数之后就不用再排了,所以这里用n-1而不是n min = i for j in range(i + 1, n): if a[j] < a[min]: min = j a[i], a[min] = a[min], a[i] print(a)a = generate_random_sequence(10, 1, 100)print(a)SelectionSort(a)'''输出结果:[77, 76, 21, 51, 29, 23, 19, 68, 53, 37][19, 76, 21, 51, 29, 23, 77, 68, 53, 37][19, 21, 76, 51, 29, 23, 77, 68, 53, 37][19, 21, 23, 51, 29, 76, 77, 68, 53, 37][19, 21, 23, 29, 51, 76, 77, 68, 53, 37][19, 21, 23, 29, 37, 76, 77, 68, 53, 51][19, 21, 23, 29, 37, 51, 77, 68, 53, 76][19, 21, 23, 29, 37, 51, 53, 68, 77, 76][19, 21, 23, 29, 37, 51, 53, 68, 77, 76][19, 21, 23, 29, 37, 51, 53, 68, 76, 77]''' 这个算法时间复杂度(算法中循环执行的次数,量级估算)为O(n^2),其中n是输入序列的长度。空间复杂度(算法运行过程中临时占用存储大小,量级估算)为O(1),因为它只需要使用常数级别的额外空间。 2. 冒泡排序通过不断比较相邻元素,将数值大的元素往后排。第一个元素和第二个元素比较,如果第一个元素大,则进行交换,再比较第二个元素和第三个元素大小,以此类推,直到最大的元素移动到最后一个位置,然后进行第二轮比较。每一轮中数值较大的元素,不断到达数组的尾部。 123456789101112131415161718192021222324def BubbleSort(a): n = len(a) for i in range(n - 1, 0, -1): # range()左闭右开,n-1可以取到右边界,逆序枚举 for j in range(0, i): if a[j] > a[j + 1]: a[j], a[j + 1] = a[j + 1], a[j] print(a)a = generate_random_sequence(10, 1, 100)print(a)BubbleSort(a)'''输出结果:[57, 86, 40, 11, 38, 46, 21, 38, 18, 6][57, 40, 11, 38, 46, 21, 38, 18, 6, 86][40, 11, 38, 46, 21, 38, 18, 6, 57, 86][11, 38, 40, 21, 38, 18, 6, 46, 57, 86][11, 38, 21, 38, 18, 6, 40, 46, 57, 86][11, 21, 38, 18, 6, 38, 40, 46, 57, 86][11, 21, 18, 6, 38, 38, 40, 46, 57, 86][11, 18, 6, 21, 38, 38, 40, 46, 57, 86][11, 6, 18, 21, 38, 38, 40, 46, 57, 86][6, 11, 18, 21, 38, 38, 40, 46, 57, 86]''' 这个算法的时间复杂度为O(n^2)(最好的情况下数据本来有序,复杂度O(n)),其中n是待排序数组的长度。空间复杂度为O(1),因为只使用了常数级别的额外空间。和选择排序算法是一样。 3. 插入排序对前i-1个数已经有序的情况下,将第i个数插入到合适的位置。 将第二个元素和第一个元素比较,如果第二个元素小于等于第一个元素,则将第一个元素向后移动,并将第一个元素执行插入,这样前两个元素就是有序的。接着进行第二轮比较,也就是将第三个元素依次和第二元素和第一个元素比较,并插入到合适的位置,使前三个元素有序。以此类推,迭代执行n-1次插入,每次插入都是将元素插入到有序序列中。 123456789101112131415161718192021222324252627def InsertionSort(a): n =len(a) for i in range(1, n): x = a[i] j = i - 1 while j >= 0 and x <= a[j]: # 若x<=a[j],则将a[j]往后移动,继续判断前一个数 a[j + 1] = a[j] j -= 1 a[j + 1] = x print(a)a = generate_random_sequence(10, 1, 100)print(a)InsertionSort(a)'''输出结果:[95, 69, 37, 81, 21, 11, 53, 12, 22, 16][69, 95, 37, 81, 21, 11, 53, 12, 22, 16][37, 69, 95, 81, 21, 11, 53, 12, 22, 16][37, 69, 81, 95, 21, 11, 53, 12, 22, 16][21, 37, 69, 81, 95, 11, 53, 12, 22, 16][11, 21, 37, 69, 81, 95, 53, 12, 22, 16][11, 21, 37, 53, 69, 81, 95, 12, 22, 16][11, 12, 21, 37, 53, 69, 81, 95, 22, 16][11, 12, 21, 22, 37, 53, 69, 81, 95, 16][11, 12, 16, 21, 22, 37, 53, 69, 81, 95]''' 这个算法的时间复杂度为O(n^2),其中n是待排序数组的长度。空间复杂度为O(1),因为只使用了常数级别的额外空间。 要改善选择排序的时间复杂度,可以考虑使用其他更高效的排序算法,例如快速排序(Quick Sort)或归并排序(Merge Sort)。这些算法的时间复杂度通常为O(nlogn),比上面三个排序算法的O(n^2)更快。此外,还可以考虑使用内置的排序函数,如Python中的sorted()函数,它使用了优化的排序算法。如果待排序数组已经基本有序,可以通过引入一些优化措施来提高插入排序的性能,例如使用二分查找来确定插入位置,减少比较和交换的次数。 4. 归并排序利用分治的思想,采用递归的形式,对元素进行排序。当有两个长度为n的有序数组时,可以通过两个指针的移动,在O(n)的时间复杂度内,快速合并成一个有序数组。而这两个长度为n的有序数组,也可以通过两个n/2的有序数组合并而来。因此,只要不断对数组进行分治,就可以把一个无序数组变成有序。 对数组拆分的过程用到递归,对数组组合的过程用到了合并,故命名归并排序。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950# 实现一个合并函数(a列表start到mid的元素,以及mid+1到end的元素,需要分别按照递增顺序排列),执行后a列表start到end的元素也按照递增顺序排序def Merge(a, start, mid, end): tmp = [] l = start # l和r是两个区间的起点 r = mid + 1 # 当两个区间都未到达右端点,判断a[l]和a[r],小的值放入临时列表,下标自增 while l <= mid and r <= end: if a[l] <= a[r]: tmp.append(a[l]) l += 1 else: tmp.append(a[r]) r += 1 # 跳出循环时,剩余部分全部放入临时列表(因为跳出循环表示一个列表已经排完了,另一个列表剩下的也是有序的) tmp.extend(a[l : mid + 1]) tmp.extend(a[r : end + 1]) # 临时列表值拷贝回原列表a,完成一次合并 for i in range(start, end + 1): a[i] = tmp[i - start] print(start, end, tmp)# 实现一个递归函数(作用是拆分子数组)def MergeSort(a, start, end): # 待排序元素只有一个则返回 if start == end: return # 计算中点mid,整数除法,向下取整 mid = (start + end) // 2 MergeSort(a, start, mid) MergeSort(a, mid + 1, end) # 两次调用MergeSort()产生两个有序数组,之后对两个有序数组进行Merge()合并 Merge(a, start, mid, end)a = generate_random_sequence(10, 1, 100)print(a)MergeSort(a, 0, 9)'''输出结果:[92, 87, 91, 24, 5, 36, 76, 62, 66, 89]0 1 [87, 92]0 2 [87, 91, 92]3 4 [5, 24]0 4 [5, 24, 87, 91, 92]5 6 [36, 76]5 7 [36, 62, 76]8 9 [66, 89]5 9 [36, 62, 66, 76, 89]0 9 [5, 24, 36, 62, 66, 76, 87, 89, 91, 92]''' 其实也可以不用递归的方法,用迭代的方式来实现归并排序: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657# 实现一个合并函数def merge(arr, start, mid, end, temp): i = start j = mid k = start while i < mid and j < end: if arr[i] < arr[j]: temp[k] = arr[i] i += 1 else: temp[k] = arr[j] j += 1 k += 1 while i < mid: temp[k] = arr[i] i += 1 k += 1 while j < end: temp[k] = arr[j] j += 1 k += 1 # 实现一个拆分子数组和合并的函数def merge_sort(arr): if len(arr) <= 1: return arr # 创建一个临时数组用于存储排序结果 temp = [0] * len(arr) # 设置初始步长为1 step = 1 while step < len(arr): # 按照步长将数组分为多个子数组进行合并 for start in range(0, len(arr), 2 * step): # 计算子数组的起始索引、中间索引和结束索引 mid = min(start + step, len(arr)) end = min(start + 2 * step, len(arr)) # 合并两个子数组 merge(arr, start, mid, end, temp) # 将临时数组的结果复制回原始数组 arr[:] = temp[:] # 增加步长 step *= 2 print(arr) return arrarr = generate_random_sequence(10, 1, 100)print(arr)sorted_arr = merge_sort(arr)'''输出结果:[65, 50, 64, 33, 58, 7, 97, 87, 92, 52] (原序列)[50, 65, 33, 64, 7, 58, 87, 97, 52, 92] (步长2)[33, 50, 64, 65, 7, 58, 87, 97, 52, 92] (步长4)[7, 33, 50, 58, 64, 65, 87, 97, 52, 92] (步长8)[7, 33, 50, 52, 58, 64, 65, 87, 92, 97] (最终序列)''' 在这个实现中,使用了一个临时数组temp来存储排序的结果。首先,设置初始步长为1,然后在每一轮迭代中,按照步长将原始数组分为多个子数组,并调用merge函数将这些子数组进行合并。合并后的结果存储在临时数组temp中。最后,将临时数组的结果复制回原始数组。迭代的思想在于通过不断迭代地合并子数组,直到得到完整的有序数组。 归并排序的时间复杂度是O(nlogn),其中n是待排序数组的大小。空间复杂度是O(n),因为在每次合并操作中需要创建一个临时列表来存储合并后的结果。 时间复杂度的算法可以参考如何计算归并排序算法的时间复杂度? 空间复杂度的算法可以参考归并排序的空间复杂度 5. 桶排序生成一些桶,让数字散列在不同桶中,对桶中元素分别执行排序,再将元素依次取出。 比如建立4个桶,遍历所有数字并依次分散到4个桶中,保证第2个桶所有数字都大于第1个桶,第3个桶所有数字都大于第2个桶。每个桶中分别进行选择排序,4个桶的元素都有序之后,再将元素依次取出。 123456789101112131415161718192021222324252627282930313233343536373839404142434445# 确定桶内排序方法(这里用选择排序)def SelectionSort(a): n = len(a) for i in range(n - 1): min = i for j in range(i + 1, n): if a[j] < a[min]: min = j a[i], a[min] = a[min], a[i]def BucketSort(a): # 确定列表元素最小值和最大值,定义桶数量 minV = min(a) maxV = max(a) bucketCount = 3 # 桶的数量 bucket = [[], [], []] # 计算每个桶的范围 perBucket = (maxV - minV + bucketCount) // bucketCount # 遍历列表每个元素,计算该元素放入的桶的索引,并且放入相应桶中 for num in a: bucketIndex = (num - minV) // perBucket bucket[bucketIndex].append(num) # 遍历每个桶,对每个桶的元素进行选择排序 for i in range(bucketCount): SelectionSort(bucket[i]) idx = 0 # 遍历每个桶,遍历桶中元素,将元素放回原列表 for i in range(bucketCount): for v in bucket[i]: a[idx] = v idx += 1 print(bucket) print(a) a = generate_random_sequence(10, 1, 100)print(a)BucketSort(a)'''输出结果:[84, 66, 53, 83, 22, 15, 95, 6, 32, 70][[6, 15, 22, 32], [53], [66, 70, 83, 84, 95]][6, 15, 22, 32, 53, 66, 70, 83, 84, 95]''' 桶的数量可以根据待排序元素的分布情况和排序的要求来决定,一般将待排序的元素均匀分配到桶中,桶内的排序算法可以是任意一种排序算法,取决于应用场景和性能要求。这个算法挺有意思的,可以看到每个桶的元素数量不一定一样,桶排序一般不会直接用,往往会做变形。 这个桶排序算法的时间复杂度为O(n + k^2),其中n是待排序元素的数量,k是桶的数量。具体来说,遍历列表并将元素放入桶中的时间复杂度为O(n),对每个桶进行选择排序的时间复杂度为O(k^2),遍历每个桶并将元素放回原列表的时间复杂度为O(n)。因此,总的时间复杂度为O(n + k^2),也就是O(n)。 空间复杂度方面,除了原始列表外,额外使用了一个大小为k的桶列表来存储元素。因此,空间复杂度为O(n + k),也就是O(n)。 6. 计数排序利用哈希表的思想,对数据类型和范围有要求。 首先生成一个计数器数组,并且一开始所有值的计数都为0,然后遍历枚举原数组的所有元素,在元素值对应的计数器上执行计数操作。最后遍历枚举计数器数组,按照数组中元素个数放回到原数组中,这样所有元素都是升序排列了。 1234567891011121314151617181920212223242526272829def CountingSort(a): n = len(a) # 获取原数组中最大值,加1,作为计数器列表的实际长度 cntlen = max(a) + 1 # 生成一个值都为0的计数器列表 cnt = [0] * cntlen # 遍历枚举原数组所有元素,在对应的计数器上加1 for val in a: cnt[val] += 1 print(cnt) n = 0 # 遍历枚举计数器列表,cnt[val]代表val这个数有多少个,大于0,则将它的计数器减一,并放到原来的列表中。如果还有则继续迭代至计数为0 for val in range(0, cntlen): while cnt[val] > 0: cnt[val] -= 1 a[n] = val n += 1a = generate_random_sequence(10, 1, 20)print(a)CountingSort(a)print(a)'''输出结果[8, 14, 18, 13, 16, 17, 7, 10, 8, 15] (原列表)[0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1] (计数器列表)[7, 8, 8, 10, 13, 14, 15, 16, 17, 18] (计数排序后的列表)''' 很明显,这种排序方式对数据有较大的限制,适用于数据范围较小的非负整数,且数据分布较均匀的情况(并不是说负数就完全不能用)。这很好理解,数据范围太广,会导致计数数组很大,占用大量内存空间,如果数据分布不均匀,计数的效率也会降低。 本质上来说这种计数排序算法是一种简单的桶排序算法,一个计数器就是一个桶。计数排序算法**时间复杂度是O(n),空间复杂度是O(n)**。 7. 基数排序和上面的计数排序很像,本质上也是桶排序,只不过用数位来划分桶。 首先建立0-9的10个桶,对待排序的每个元素,按照个位数的值放入对应的桶中,按顺序遍历桶中元素,取出来放回原数组。对于待排序的每个数字,按照十位数的值放入对应桶中,按顺序遍历桶中元素,取出来放回原数组。以此类推,按照百位数、千位数的值放入桶中,遍历,取出,直到排序完成。 123456789101112131415161718192021222324252627282930313233def RadixSort(a): base = 1 # base为取的数位 maxv = max(a) # 从低到高遍历每个数位 while base < maxv: bucket = [] # 每次遍历定义10个桶 for idx in range(10): bucket.append([]) # 每个原列表元素根据当前数位放入对应的桶中 for num in a: idx = num // base % 10 # //向下取整,%除法取余 bucket[idx].append(num) l = 0 # 遍历每个桶,按顺序放回原列表 for idx in range(10): for v in bucket[idx]: a[l] = v l += 1 print(a) base *= 10a = generate_random_sequence(10, 1, 1000)print(a)RadixSort(a)'''输出结果:[119, 13, 832, 247, 117, 126, 996, 904, 112, 396] (原序列)[832, 112, 13, 904, 126, 996, 396, 247, 117, 119] (按照个位数放入桶中,遍历取出)[904, 112, 13, 117, 119, 126, 832, 247, 996, 396] (按照十位数放入桶中,遍历取出)[13, 112, 117, 119, 126, 247, 396, 832, 904, 996] (按照百位数放入桶中,遍历取出)''' 上面的代码只适用于正整数,我们通过循环遍历每个数位,所以外层循环的次数是位数d。内层循环中,我们遍历了待排序数组并将每个元素放入对应的桶中,所以内层循环的时间复杂度是O(n)。最后,我们遍历每个桶,按顺序将元素放回原列表,这也需要O(n)的时间复杂度。 总的来说,基数排序的时间复杂度为O(d * (n + k)),其中d表示位数,n表示待排序数组的长度,k表示桶的数量。空间复杂度为O(dk+n)。 如果有负整数,可以把原数组元素分为正整数和负整数,分别进行基数排序后合并: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152def RadixSort(a): # 分离正数和负数 positive_nums = [num for num in a if num >= 0] negative_nums = [-num for num in a if num < 0] # 对正数部分进行基数排序 if len(positive_nums) > 0: base = 1 maxv = max(positive_nums) while base < maxv: bucket = [[] for _ in range(10)] for num in positive_nums: idx = num // base % 10 bucket[idx].append(num) positive_nums = [v for bucket_list in bucket for v in bucket_list] print(positive_nums) base *= 10 # 对负数部分进行基数排序 if len(negative_nums) > 0: base = 1 maxv = max(negative_nums) while base < maxv: bucket = [[] for _ in range(10)] for num in negative_nums: idx = num // base % 10 bucket[idx].append(num) negative_nums = [v for bucket_list in bucket for v in bucket_list] print(negative_nums) base *= 10 # 将负数部分反转 negative_nums = [-num for num in negative_nums[::-1]] print(negative_nums) # 合并正数和负数部分 sorted_a = negative_nums + positive_nums return sorted_aa = generate_random_sequence(10, -1000, 1000)print(a)a = RadixSort(a)print(a)'''输出结果:[-918, -574, 385, -322, -164, 880, -158, -400, 607, -746] (原序列)[880, 385, 607][607, 880, 385][385, 607, 880] (基数排序后的正数序列)[400, 322, 574, 164, 746, 918, 158][400, 918, 322, 746, 158, 164, 574][158, 164, 322, 400, 574, 746, 918] (基数排序后取反的负数序列)[-918, -746, -574, -400, -322, -164, -158] (反转的成原来的负数序列)[-918, -746, -574, -400, -322, -164, -158, 385, 607, 880] (整合排序后的结果)''' 8. 快速排序找到一个基准点,把小于它的和大于它的数分开,分别递归执行快速排序。也是用了分治的思想,属于冒泡排序的改进算法。 12345678910111213141516171819202122232425262728293031323334353637383940# 从a列表start到end之间寻找基准数下标,并且将所有小于等于它的数放在它左边,大于它的数放在右边def QuickSortPivot(a, start, end): pivot = start # 最左边数为基准数 j = start + 1 # j代表大于基准数的数的下标左边界 # 遍历列表所有数,如果当前数小于等于基准数,则a[i]和a[j]交换,j自增;大于则不处理(保证j下标以前的数小于等于基准数) for i in range(start + 1, end + 1): if a[i] <= a[pivot]: a[i], a[j] = a[j], a[i] j += 1 # 遍历之后,基准数与小于基准数的最后一个数交换(这样就可以让基准数左边和右边分开,且基准数位置就是正确的了) a[pivot], a[j - 1] = a[j - 1], a[pivot] # 更新基准数下标 pivot = j - 1 print(a[pivot], a[start : pivot], a[pivot + 1 : end + 1]) return pivot# 快速排序函数,用来对区间[start, end]的数递归执行快速排序def QuickSort(a, start, end): if start >= end: return # 获得基准数下标,分别递归计算左边和右边部分 pivot = QuickSortPivot(a, start, end) QuickSort(a, start, pivot - 1) QuickSort(a, pivot + 1, end)a = generate_random_sequence(10, 1, 100)print(a)QuickSort(a, 0, 9)print(a)'''输出结果:[55, 100, 41, 99, 52, 90, 50, 71, 92, 44] # 原序列55 [44, 41, 52, 50] [90, 99, 71, 92, 100] # 基准数,基准数左边序列,基准数右边序列44 [41] [52, 50]52 [50] []90 [71] [99, 92, 100]99 [92] [100][41, 44, 50, 52, 55, 71, 90, 92, 99, 100] # 快速排序后的序列''' 这种排序算法也有一个缺点,如果很不巧每次选取的基准点都是序列的最大值或者最小值,那么时间复杂度将会是最大值O(n^2)。 时间复杂度: 在每一次划分操作中,需要遍历待排序数组的所有元素,这需要O(n)的时间。 在每一次划分操作中,将数组划分为两个子数组,每个子数组的长度大约是原数组的一半(最好的情况)。因此,划分操作的时间复杂度为O(n)。 快速排序的递归深度为logn,因为每次划分操作都将数组的规模减半。 因此,最好的情况下时间复杂度为O(nlogn),最坏情况下时间复杂度为O(n^2)。 空间复杂度: 快速排序使用递归调用来对子数组进行排序,每次递归调用都需要保存当前的函数调用信息(包括参数、局部变量等)。 快速排序的递归深度通常为logn,因此需要的栈空间也是logn。 因此,总的空间复杂度为O(logn)。最坏的情况下是O(n),随机化基准值pivot可以防止最坏情况发生。 可以用随机快速排序,每次找基准点采用了一次随机,规避快速排序最坏的情况发生。 1234567891011121314151617181920212223242526272829303132333435363738394041import randomdef QuickSortPivot(a, start, end): # 引入区间中随机一个元素的索引值,和最左边的数交换(本来是最左边的数作为下一轮的基准数) randIdx = random.randint(start, end) a[start], a[randIdx] = a[randIdx], a[start] pivot = start j = start + 1 for i in range(start + 1, end + 1): if a[i] <= a[pivot]: a[i], a[j] = a[j], a[i] j += 1 a[pivot], a[j - 1] = a[j - 1], a[pivot] pivot = j - 1 print(a[pivot], a[start : pivot], a[pivot + 1 : end + 1]) return pivotdef QuickSort(a, start, end): if start >= end: return pivot = QuickSortPivot(a, start, end) QuickSort(a, start, pivot - 1) QuickSort(a, pivot + 1, end) a = generate_random_sequence(10, 1, 100)print(a)QuickSort(a, 0, 9)print(a)'''输出结果:[24, 2, 44, 75, 32, 32, 68, 36, 9, 79]68 [9, 2, 44, 32, 32, 24, 36] [75, 79]36 [9, 2, 32, 32, 24] [44]9 [2] [32, 32, 24]24 [] [32, 32]32 [32] []79 [75] [][2, 9, 24, 32, 32, 36, 44, 68, 75, 79]''' 9. 希尔排序本质是一种改进后的插入排序,又称“缩小增量排序”,增量在这里是指按照一定规则选择的间隔值(这里也是分组数),通常设置为数组长度的一半,每次缩小增量直到增量为1,组内排序方法为插入排序。每轮希尔排序的分组数越来越小,也就是说组内元素越来越多,最后一组就是整个数组。 123456789101112131415161718192021222324252627282930def ShellSort(a): n = len(a) # gap为增量,每隔gap值执行插入排序 gap = n // 2 while gap > 0: for i in range(gap, n): x = a[i] j =i while j >= gap: if x < a[j - gap]: a[j] = a[j - gap] else: break j -= gap a[j] = x print(a) # 增量除2向下取整,继续迭代 gap = gap // 2a = generate_random_sequence(10, 1, 100)print(a)ShellSort(a)'''输出结果:[20, 17, 75, 69, 34, 85, 38, 23, 57, 28] # 原序列[20, 17, 23, 57, 28, 85, 38, 75, 69, 34] # 第一次希尔排序,实际分了5组[20,85][17,38][75,23][69,57][34,28]并组内进行了插入排序[20, 17, 23, 34, 28, 57, 38, 75, 69, 85] # 第二次希尔排序,实际分了2组[20,23,28,38,69][17,57,85,75,34]并组内进行了插入排序[17, 20, 23, 28, 34, 38, 57, 69, 75, 85] # 第三次希尔排序,实际就是1组,所有组内元素插入排序''' 希尔排序的时间复杂度是根据增量序列的选择而变化的。在最坏情况下(原序列为逆序),时间复杂度是O(n^2);最好的情况下(原序列本身有序),时间复杂度是O(n)。平均情况下,希尔排序的时间复杂度可以达到O(nlogn)。 希尔排序的空间复杂度是O(1),即不需要额外的空间来存储数据。希尔排序是一种原地排序算法,它通过交换元素的位置来实现排序,不需要额外的辅助数组或链表。因此,希尔排序的空间复杂度是常数级别的。 10. 堆排序堆是一颗完全二叉树,假设一个节点编号为idx,则左子树树根编号为idx*2,右子树树根编号为idx*2+1,把每个结点的编号和顺序表下标对应,就可以把这颗二叉树用顺序表形式存储起来。 对于一个给定的顺序表,首先构造成大顶堆(所有根结点大于左右子结点),方法是从顺序表的最后一个元素开始,自底向上构造堆。对于某个元素,取左右子树中的大者和该元素比较,如果比该元素大,则与之交换(该元素所在位置下沉)。由于每个堆左右子树都是满足堆的性质的,当枚举完根结点后大顶堆就构造完毕了。 这个时候堆顶的元素是最大的,把堆顶元素和顺序表最后一个元素进行交换(弹出),最大值就在最后一位了。继续利用堆的下沉操作,除了最后一位以外继续构造大顶堆,把次大元素交换到倒数第二位,依此类推,最终整个顺序表就是升序排列的有序顺序表了。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748# 实现大顶堆下沉操作,heap整个顺序表start到end之间组成合法的堆,start为根结点坐标def maxHeapify(heap, start, end): son = start * 2 # 左子树根 # 左子树存在,则取左子树和右子树根中的大者的下标,存储到son中 while son <= end: if son + 1 <= end and heap[son + 1] > heap[son]: son += 1 # 如果结点点的值大于根结点的值,则将根结点和子结点交换(下沉),子结点迭代执行 if heap[son] > heap[start]: heap[start], heap[son] = heap[son], heap[start] start, son = son, son * 2 # 如果子结点的值小于等于根结点的值,说明堆构造没问题,跳出循环 else: break# 实现堆排序操作def HeapSort(a): # 堆下标从1开始,列表下标从0开始,所以在0的位置加上占位符None heap = [None] + a # 定义堆顶元素下标root为1 root = 1 l = len(heap) # 从顺序表l//2个元素开始(因为堆的最后一层是没有子结点的)逆序枚举,自底向上构造堆 for i in range(l // 2, root - 1, -1): maxHeapify(heap, i, l - 1) # 堆顶和最后一个元素交换,除最后一个元素外,重构堆 for i in range(l - 1, root, -1): heap[i], heap[root] = heap[root], heap[i] maxHeapify(heap, root, i - 1) print(heap[root:])a = generate_random_sequence(10, 1, 100)print(a)HeapSort(a)'''输出结果:[3, 52, 44, 78, 60, 100, 54, 61, 38, 1] # 原数列[78, 61, 54, 52, 60, 44, 3, 1, 38, 100] # 最大数为100,1-9位重新构造大顶堆[61, 60, 54, 52, 38, 44, 3, 1, 78, 100] # 次大数为78,1-8位重新构造大顶堆[60, 52, 54, 1, 38, 44, 3, 61, 78, 100] # 第三大数为61,1-7重新构造大顶堆[54, 52, 44, 1, 38, 3, 60, 61, 78, 100] .[52, 38, 44, 1, 3, 54, 60, 61, 78, 100] .[44, 38, 3, 1, 52, 54, 60, 61, 78, 100] .[38, 1, 3, 44, 52, 54, 60, 61, 78, 100] .[3, 1, 38, 44, 52, 54, 60, 61, 78, 100] .[1, 3, 38, 44, 52, 54, 60, 61, 78, 100] # 最后一次构造大顶堆,只有一个最小元素1,此时数列已经升序排好''' 堆排序的时间复杂度为O(nlogn),n是待排序元素的数量,空间复杂度为O(1),因为它是原地排序算法,不需要额外的空间来存储元素。 最后是一张来自菜鸟教程的排序算法总结图:","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"编程自学","slug":"编程自学","permalink":"http://www.shelven.com/categories/%E7%BC%96%E7%A8%8B%E8%87%AA%E5%AD%A6/"}],"tags":[{"name":"python","slug":"python","permalink":"http://www.shelven.com/tags/python/"}]},{"title":"群体基因组学——GWAS分析","slug":"群体基因组学——GWAS分析","date":"2023-06-20T10:40:24.000Z","updated":"2023-06-24T07:28:17.000Z","comments":true,"path":"2023/06/20/a.html","link":"","permalink":"http://www.shelven.com/2023/06/20/a.html","excerpt":"本篇笔记主要记录如何在linux命令行中用TASSEL5和PLINK2软件做GWAS分析,以及如何可视化GWAS结果(在R中绘制曼哈顿图和QQ图)。关于GWAS是做什么的,以及基本概念介绍可以看上一篇笔记介绍:群体基因组学——概述和图表详解 - 我的小破站 (shelven.com)","text":"本篇笔记主要记录如何在linux命令行中用TASSEL5和PLINK2软件做GWAS分析,以及如何可视化GWAS结果(在R中绘制曼哈顿图和QQ图)。关于GWAS是做什么的,以及基本概念介绍可以看上一篇笔记介绍:群体基因组学——概述和图表详解 - 我的小破站 (shelven.com) PLINK和TASSEL都是做GWAS分析用的经典软件,两个软件都有linux版本和windows版本,我这里是在linux集群环境中跑的分析流程,所有软件下载linux版本,两款软件可以在下方官网中安装: PLINK 2.0 (cog-genomics.org)、Tassel TASSEL官网有tassel pipeline命令行的帮助文档,一些常用的参数可以参考Tassel5PipelineCLI.pdf (bytebucket.org) TASSEL中文文档可以参考本站Tassel5中文操作手册.pdf 1. 数据格式plink的数据格式有两套,每套各自的前缀名称相同,一套后缀为.bed、.bin和.fam,另一套后缀为.map和.ped,后面的分析以第一套为例,读取速度比较快。 那么这三个是怎么来的呢?我们做群体重测序后,通过GATK等软件做变异检测最终会生成一个VCF文件,通过软件plink可以将VCF文件转化成上面的三个文件,分别展示一下三个文件的内容和结构: fam文件(家族信息文件)有6列,分别为家系编号、个体编号、父系编号(0表示信息缺失)、母系编号(0表示信息缺失)、性别编号(1表示男,2表示女,0表示未知性别)、表型值(1表示对照,2表示病例,0/-9或者其他非数字表示信息缺失) bim文件(个体信息文件)有6列,分别为染色体编号、SNP标识、以摩根或厘摩为单位的位置(可以用0)、碱基对坐标、Allele1和Allele2(通常是主效等位基因) bed文件为二进制文件 转化成这三个文件主要是为了下一步更快过滤数据,因为bed是二进制文件,计算机处理起来更快。 以上数据来源是贺师兄做的棉花重测序样本的部分变异信息文件。 2. 基因型数据清洗(使用PLINK)使用plink软件过滤掉最小等位基因频率(Minor Allele Frequency,MAF)小于0.05的变异位点,在关联分析中MAF值太小会造成假阳性。比如说,一个基因座上有两个等位基因A和B,A在群体中的频率为0.6,B在群体中的频率为0.4,那么MAF值就是0.4,可以想到一个基因座上MAF值越小,基因座上的等位基因越单一,MAF太低的位点贡献的信息很少,不与表型做关联分析。 1234567# 过滤MAF小于0.05的SNP,重新生成.bed、.bin和.fam文件## --make-bed 创建PLINK1二进制文件集plink --bfile Course_GWAS --maf 0.05 --make-bed --out Course_GWAS_maf0.05# 重新将三个过滤后的文件转化成vcf文件## --recode 创建新文件集,可选格式'vcf','vcf-fid',和'vcf-iid'plink --bfile Course_GWAS_maf0.05 --recode vcf-iid --out GWAS_vcf 可以通过查看过滤前后的bim文件,统计过滤了多少位点(plink软件在屏幕上的标准输出也会显示): 123456789101112131415161718192021222324# plink输出结果PLINK v2.00a5LM 64-bit Intel (7 Jun 2023) www.cog-genomics.org/plink/2.0/(C) 2005-2023 Shaun Purcell, Christopher Chang GNU General Public License v3Logging to Course_GWAS_maf0.05.log.Options in effect: --bfile Course_GWAS --maf 0.05 --make-bed --out Course_GWAS_maf0.05Start time: Tue Jun 20 14:38:58 20231837 MiB RAM detected, ~914 available; reserving 850 MiB for main workspace.Using up to 2 compute threads.216 samples (0 females, 0 males, 216 ambiguous; 216 founders) loaded fromCourse_GWAS.fam.10000 variants loaded from Course_GWAS.bim.Note: No phenotype data present.Calculating allele frequencies... done.3030 variants removed due to allele frequency threshold(s)(--maf/--max-maf/--mac/--max-mac).6970 variants remaining after main filters.Writing Course_GWAS_maf0.05.fam ... done.Writing Course_GWAS_maf0.05.bim ... done.Writing Course_GWAS_maf0.05.bed ... done. 需要注意下我这里使用的表型数据都是处理好的,所以只对基因型数据做过滤就行,如果自己有表型数据还要做一下表型数据清洗,比如删除异常值等等。 生成的GWAS_vcf.vcf用于下一步关联分析。 3. 关联分析(使用TASSEL5)PLINK软件也可以做关联分析,网上也能找到一把教程,但是PLINK只能做GLM模型的关联分析,现在文献中用的比较多的软件是TASSEL5,接下来演示用命令行的Tassel Pipeline做关联分析。 3.1 计算亲缘关系矩阵12345678# -Xmx10G 控制最大堆(heap size)的大小为10G# -importGuess 使用Tassel的importGuess函数载入文件# -KinshipPlugin 调用亲缘关系矩阵插件,与 -endPlugin 联用# -method 这里指定计算IBS亲缘关系矩阵# -export 指定输出文件,输出文件类型与input数据有关,比如基因型表默认Hapmap格式,距离矩阵默认SqrMatrix# -exportType 指定输出文件类型,有Hapmap, HDF5, VCF, Plink, SqrMatrix等等perl /opt/TASSEL5/run_pipeline.pl -Xmx10G -importGuess GWAS_vcf.vcf -KinshipPlugin -method Centered_IBS -endPlugin -export kinship.txt -exportType SqrMatrix 生成的216个样本的亲缘关系矩阵kinship.txt如下: 3.2 主成分分析123456# -fork<id> 用于区分不同的pipeline,代表一个pipeline的起始,后面可以是数字或者字符(非空格)# -PrincipalComponentsPlugin 调用主成分分析插件,同样和 -endPlugin 联用# -covariance true 计算协方差矩阵(用来识别主成分),两种方法,相关系数(correlation)或者协方差(covariance)# -runfork<id> 这个参数已经可以不需要了,pipeline会自动执行需要的forkperl /opt/TASSEL5/run_pipeline.pl -fork1 -importGuess GWAS_vcf.vcf -PrincipalComponentsPlugin -covariance true -endPlugin -export pca -runfork1 默认情况下会生成的PCA结果主要展示前5个主成分,且这一步会生成三个txt文件,PCA结果展示在第一个文件pca1.txt: 3.3 可视化亲缘关系矩阵和PCA结果(群体结构分析)在拿到这两个矩阵之后就可以通过R可视化结果,首先是亲缘关系矩阵热图: 1234567891011library(data.table)kinship = fread("kinship.txt",skip = 3) # 前3行不用读入setDF(kinship) # 将data.table格式转化成data.frame格式row.names(kinship) = kinship$V1kinship$V1 = NULL colnames(kinship) = row.names(kinship) # 第一列做行名,删除第一列,行名设置为列名 kinship = as.matrix(kinship) # 转成矩阵格式pdf("kinship.pdf")heatmap(kinship,labRow=F,labCol=F) # 绘制热图,不在行和列上显示标签dev.off() 用颜色深浅不同表示每个样本之间的亲缘关系远近,颜色越深亲缘关系越近。 PCA结果作图: 123456789library(data.table)library(ggplot2)pca_re = fread("pca1.txt",skip = 2)plot = ggplot(pca_re, aes(x=PC1, y=PC2)) + geom_point(size=2) + geom_hline(yintercept = 0) + # 添加x坐标 geom_vline(xintercept = 0) + # 添加y坐标 theme_bw()ggsave(plot =plot, filename="2D-PCA.pdf") # 以第一主成分为X轴,第二主成分为y轴作图即可 这里的群体结构差异有但不能很好分类。如果做群体结构分析的时候可以分成明显的几个不同的部分,那就要考虑后续做GWAS分析过程中要考虑群体结构的分层问题了。这里我们只是拿部分数据跑个简单的流程,继续往下。 3.4 基于GLM模型进行GWAS分析现在分别有了如下的基因型数据(GWAS_vcf.vcf)、表型数据(phenotype.tassel.txt),就可以以PCA结果作为协变量,基于GLM模型进行GWAS分析: 12345# -excludeLastTrait 移除表型数据的最后一列(不太明白什么意思?官网说可用于删除的最后一列用于MLM或GLM的群体结构)# -combine<id> 用在新的pipneline开头,将多个来自不同pipeline的数据集组合到一起,后面使用 -input<id> 指定,以 -intersect 结尾# -FixedEffectLMPlugin 使用GLM模型perl /opt/TASSEL5/run_pipeline.pl -fork1 -importGuess GWAS_vcf.vcf -fork2 -importGuess phenotype.tassel.txt -fork3 -importGuess pca1.txt -excludeLastTrait -combine5 -input1 -input2 -input3 -intersect -FixedEffectLMPlugin -endPlugin -export glm 主要结果为glm1.txt,结果如下: 我们主要用到的数据是: 第一列:表型 第二列:SNP名称 第三列:染色体编号 第四列:处于染色体的位置 第六列:p值 3.5 基于MLM模型进行GWAS分析在tassel中运行MLM模型和GLM模型相似,MLM模型需要亲缘关系矩阵来定义个体之间的关系,以PCA和kinship作为协变量,基于MLM模型进行GWAS分析: 123# -mlm -mlmVarCompEst P3D -mlmCompressionLevel Optimum 使用P3D和最优水平压缩perl /opt/TASSEL5/run_pipeline.pl -fork1 -importGuess GWAS_vcf.vcf -fork2 -importGuess phenotype.tassel.txt -fork3 -importGuess pca1.txt -fork4 -importGuess kinship.txt -combine5 -input1 -input2 -input3 -intersect -combine6 -input5 -input4 -mlm -mlmVarCompEst P3D -mlmCompressionLevel Optimum -export mlm 主要结果为mlm6.txt(前5个是5个表型的分析数据),结果如下: 我们主要用到的数据是: 第一列:表型 第二列:SNP名称 第三列:染色体编号 第四列:处于染色体的位置 第七列:p值 3.6 计算核酸多态性值1vcftools --vcf GWAS_vcf.vcf --site-pi --out pi 用vcftools就可以根据vcf文件统计平均每个位点的π值。 4. GWAS结果可视化1234567891011121314151617181920212223242526272829303132# qqman包绘制曼哈顿图和qq图library(qqman)library(data.table)glm <- fread("glm1.txt")[,c("Trait","Marker","Chr","Pos","p")]names(glm) <- c("Trait","SNP","CHR","BP","P")mlm <- fread("mlm6.txt")[,c("Trait","Marker","Chr","Pos","p")]mlm <- na.omit(mlm) # 需要注意mlm结果文件有一行是NaN值,去除names(mlm) <- c("Trait","SNP","CHR","BP","P") # 自定义作图函数plot_func <- function(data,trait,model){ pdf(paste(model,"_",trait,".manhttan.pdf",sep = ""), width = 11,height = 6) manhattan(data[Trait==trait,2:5], # 曼哈顿图需要的是"SNP","CHR","BP","P"这几列信息 suggestiveline = F, genomewideline = F) # remove the suggestive(Default -log10(1e-5)) and genome-wide(Default -log10(5e-8)) significance lines,也就是去掉两条阈值线 dev.off() pdf(paste(model,"_",trait,".qq.pdf",sep = "")) qqman::qq(data[Trait==trait,]$P) # qq图只要获得每个SNP位点的p值就可以作图 dev.off()}# 区分下不同表型for (trait in unique(glm$Trait)){ plot_func(glm,trait,"glm")}for (trait in unique(mlm$Trait)){ plot_func(mlm,trait,"mlm")} 生成各个表型GWAS分析的曼哈顿图和qq图: 随便查看一个MLM模型的GWAS曼哈顿图和qq图结果:","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"群体基因组学","slug":"群体基因组学","permalink":"http://www.shelven.com/categories/%E7%BE%A4%E4%BD%93%E5%9F%BA%E5%9B%A0%E7%BB%84%E5%AD%A6/"}],"tags":[{"name":"群体基因组学","slug":"群体基因组学","permalink":"http://www.shelven.com/tags/%E7%BE%A4%E4%BD%93%E5%9F%BA%E5%9B%A0%E7%BB%84%E5%AD%A6/"},{"name":"Tassel5","slug":"Tassel5","permalink":"http://www.shelven.com/tags/Tassel5/"},{"name":"Plink","slug":"Plink","permalink":"http://www.shelven.com/tags/Plink/"}]},{"title":"群体基因组学——概述和图表详解","slug":"群体基因组学——概述和图表详解","date":"2023-06-16T08:47:16.000Z","updated":"2023-06-16T08:57:50.000Z","comments":true,"path":"2023/06/16/a.html","link":"","permalink":"http://www.shelven.com/2023/06/16/a.html","excerpt":"最近看群体遗传学的文章,苦于不能理解研究者做的另人眼花缭乱的图表……哎,该补补基础了。这篇笔记先从樊龙江主编的《植物基因组学》教材开始学习基础概念,有部分摘自知乎和一些文献,加上这个领域论文图表的解读和自己的理解做汇总整理。下一篇笔记自己实操跑一下GWAS流程。","text":"最近看群体遗传学的文章,苦于不能理解研究者做的另人眼花缭乱的图表……哎,该补补基础了。这篇笔记先从樊龙江主编的《植物基因组学》教材开始学习基础概念,有部分摘自知乎和一些文献,加上这个领域论文图表的解读和自己的理解做汇总整理。下一篇笔记自己实操跑一下GWAS流程。 1. 群体基因组学概述群体基因组学,将基因组数据和技术与群体遗传学理论体系结合,通过覆盖全基因组范围的多态性推测全基因组效应和位点特异性效应。说人话,这门学科的作用就是通过检测SNP等的信息来回答关于物种起源、进化以及环境适应的分子机制。 群体基因组研究的基础是高质量的群体基因型数据,在获得群体原始测序数据后,如何高效准确的进行变异检测和群体基因分型是后续研究的关键(因为要用到大量的测序数据,所以也很烧钱)。 变异检测的流程可分为数据质控——读序联配——变异检测——基因分型 上图的bam文件通过软件GATK检测结构变异或者SNP就是变异检测,这部分内容我前面组装三代基因组测序数据中使用过,因为我只有一个二倍体基因组,所以当时也没用到基因分型,GATK用法可以看我的这篇笔记: 0基础学习三代基因组测序组装(9)——GATK检测植物基因组SNP和INDEL变异 - 我的小破站 (shelven.com) 2. 群体基因组进化分析方法2.1 群体系统发生树和群体结构群体发生树反映特定群体中个体间亲缘关系。一般我们做进化树都是用单拷贝基因,而做群体研究构建进化树用了整个基因组数据信息(比如全基因组SNP、Indel、SV等),能一定程度抵消横向基因转移和单个基因速率差异带来的分析误差,比单基因构建的结果更接近真实进化关系。全基因组SNP构建进化树(一般是把SNP merge到一起),个体间两两比较,根据SNP差异计算个体差异距离,构建群体差异矩阵。软件有MEGA、PHYLIP、FastTree等。 还有一个非常重要的概念:群体遗传结构,指基因型或者基因在空间和时间上的分布模式,包括种群内的遗传变异和种群间的遗传分化。种群遗传结构反映物种进化历史中的一些特殊进化事件,软件有Structure、Frappe等。总体流程确定亚群数(K),然后计算各个体归属第K亚群的概率Q值。以及主成分分析(PCA)也常用于群体结构划分。 上图是华农王茂军老师课题组做的不同二倍体棉花的物种发生树和群体结构。 2.2 群体遗传分化2.2.1 固定系数 Fst在明确一个物种存在群体结构后,通产需要量化该物种亚群间的分化程度。固定系数(Fst)反映群体等位基因杂合性水平,Fst越大,种群间遗传分化程度越大。 Fst= (Ht-Hs)/HtHs:亚群体中的平均杂合度Ht:复合群体中的平均杂合度 2.2.2 核酸多态性 𝝅做群体的文献中经常出现这个指标类似𝝅野生/𝝅栽培这样的系数,核酸多态性(Nucleotide diversity)是用于衡量群体的多态性的,为群体内平均每两个样本每个位点的差异核苷酸数量。一般称为𝝅,自然群体多态性一般高于栽培群体。 需要注意下这个𝝅的单位,一般都在 10^(-3) 这个数量级。 这些群体分化的相关系数计算可以用软件Arlequin、FSTAT、VCFtools等。 2.3 基因流(渐渗)基因流(也称为基因迁移)指从一个物种的一个种群向另一个种群引入遗传物质,从而改变群体的遗传组成。 当一个种群中的一个个体迁移到另一个群体,就会把基因带到新的群体,也就是产生“基因的流动”。基因流越大,群体间的相似性越大,会导致群体间基因频率和基因型频率呈现哈迪-温伯格平衡(在一个随机交配的大群体中,没有突变、选择等外界环境因素的影响,基因频率和基因型频率世代恒定)。 自然选择和遗传漂变会使群体之间的差异增加,而基因流的作用是“弱化”群体间的遗传差异,群体之间趋于一致。在植物中,种子扩散和花粉传播是植物基因流的最主要方式。从上面的描述也能看出,基因流与Fst成反比;与种群间地理距离成反比。检测方法主要有D-统计(ABBA-BABA)、Treemix、Migrate-N等。 2.4 种群动态和进化历史种群动态指种群大小或密度随时间或空间的变化。 有效群体大小(Ne)指与实际群体有相同基因频率方差或者相同杂合度衰减率的理想群体含量,通常小于绝对群体大小,决定群体平均近郊系数增量大小,反映群体遗传结构中基因的平均纯和速度。种群动态理论与方法主要包括种群大小及其变化、种群生长模式的量化描述,以及引起种群变化的外在环境因素。基于全基因组的种群历史动态分析方法主要有PSMC、MSMC等。 上图展示了不同地理来源的拟南芥的有效群体大小变化。可以看到拟南芥在9万~12万年前,由祖先群体在非洲开始分离形成亚群。欧亚祖先群体在 8 万年前从非洲分化形成,然后欧亚拟南芥群体在4万年前进一步分化。这一模式与包括人类在内的多种物种分化时间模式非常相似,这意味着间冰期和洪积世时期(即9万~12万年前)的气候事件对物种分布十分重要。 3. 基因组选择信号自然群体区别于栽培群体的最大特征是丰富的遗传多样性。草本作物祖先自然群体多态性(𝝅)一般在 0.003~0.008,高于相应的驯化作物遗传多态性。 遗传瓶颈效应:由于环境骤变或人类活动,某一生物种群的规模迅速减少,仅有少部分个体能够顺利通过瓶颈事件,之后可能经历一段恢复期并产生大量后代。由于连锁不平衡(LD)的作用,不仅仅是驯化基因的多态性降低,其侧翼区域遗传多样性也会降低。 有害性突变(deleterious SNP,dSNP)指导致生物个体的整体适合度(fitness)减低的突变。植物被驯化的过程中,基因组上有害突变的数量、频率会不断增加和累积,导致驯化后的作物在原本自然环境中适合度降低,称为作物的“驯化成本”。 在作物群体基因组中,一个明显的特征是存在大量来自野生祖先种的遗传渐渗。来自野生群体的基因渗入可以有效增加栽培种的环境适应性。 选择分析可以在宏观和微观两个尺度下进行选择基因和信号鉴定。 3.1 宏观进化尺度 宏观进化尺度 基于基因:Ka/Ks(非同义替换比同义替换)、MKT检验(比较物种内和物种间的Ka/Ks值) 基于进化速率:HKA 宏观尺度检测选择信号的方法,通常在亲缘关系相近的物种之间进行同源基因序列的比较,判断基因是否在某一物种或进化分支上存在加速进化的情况。 判断加速进化的背景指标是Ka/Ks,比较每个位点区域中非同义替换率与每个位点的同义替换率。同义突变总体是中性进化的(中性基准),如果存在过多的非同义突变意味着存在倾向于蛋白序列改变的正向选择(正选择表现为固定某种有利等位基因,负选择表现为清除不利的等位基因)。MKT检验本质是比较物种内和物种间的Ka/Ks值,中性情况下相等,如果物种间的比值显著高于物种内比值,意味着物种中存在正向选择信号,如果是在作物驯化中,可能是人工选择的信号。 3.2 微观进化尺度 微观进化尺度 基于等位基因频谱:Tajima’s D, Fu andLi’sD, Fay and Wu’sH, CLR, Hp 基于连锁不平衡:LRH、iHS、连锁不平衡衰减(LDD)、IBD分析 基于群体分化:LKT、LSBL 组合方法:CLR、XP-CLR(适合SNP大数据集)、DH检验、CMS 微观尺度鉴定方法,在正向选择的作用下,有利等位基因在群体中频率会变高甚至固定。当有利等位基因和周围变异达到较高频率的时候,这段区间在群体水品就会呈现遗传多态性降低的现象,也就是选择性清除(selective sweep)。 比如,栽培物种在选择性清除区域的遗传多样性显著降低,就是驯化区域的典型特征。选择性清除还会使连锁不平衡区块延长、群体间固定指数增大、Tajima‘s D值为负且显著偏离零。中性模型为基础的检测方法很多,Tajima‘s D检验是最经典的。 总体来说,选择信号检测原理和方法可以分为以下5类: A 基于群体多态性。原理:受选择位点两侧序列多态性因连带效应保持很低水平。在驯化选择区间内,𝝅野生/𝝅栽培的值变高。 B 基于等位基因频谱。原理:有利突变不断在群体中固定,当完全固定时,周围可能由于随机突变出现稀有等位基因 C 基于连锁不平衡。原理:选择性清除会引起包含等位基因的单倍体纯和性提高,与周围变异的连锁不平衡程度变高。当群体处于正向选择作用下时,基因突变及其连锁位点在正选择的作用下,在短时间内会达到较高频率,形成大片段的纯合单倍型。 D 基于群体分化。随着受选择的等位基因频率上升,导致与原群体的Fst变大。Fst的取值范围为0-1,1表示群体间完全分化的位点,0表示在群体间完全没有分化的位点。 E 组合方法。 在检测选择信号时,需要考虑遗传漂移(genetic drift)对选择信号的影响,为了降低该影响,通常在全基因组扫描窗口的基础上,仅考虑极端值(前5%)区域作为选择位点(这点很重要,后面实操的时候会说)。 选择信号检测方法都是基于自下而上的,通过群体基因组扫描鉴定具有选择信号的基因组区域,从而挖掘可能与某种表型或者适应性相关联的基因,假阳性高。 也可以用自上而下的方法进行佐证,比如获得该物种多个时间点或者多个世代的群体基因组数据,就可以直接检测该位点等位基因频率变化加以验证,比如用QTL、GWAS等传统定位方法。 4. 连锁不平衡(LD)连锁不平衡理论对群体研究非常重要,前面说到基于连锁不平衡的原理检测选择信号,这里再加深一下理解。 连锁不平衡(linkage disequilibrium,LD):在某一群体中,两个基因同时遗传的频率大于随机组合的频率。 连锁不平衡的三个衡量指标: D值:D = P(AB) - P(A) X P(B),也就是实际概率减去独立遗传的理论概率,D值绝对值越大,连锁程度越大。 D‘值:理解维归一化之后的D值,归一化之的值可以用于比较不同基因连锁程度的大小。0表示完全连锁平衡,独立遗传;1表示完全连锁不平衡。 r平方 r值定义如下: 通常文献里也是用r平方来表征连锁不平衡程度,r平方等于0时,表示完全连锁平衡,独立遗传;r平方等于1时, 表示完全连锁不平衡。 这里就要引出另一个很重要的概念——LD衰减,也是做GWAS出现最多的图。 4.1 LD衰减曲线LD衰减指位点间由连锁不平衡到连锁平衡的演变过程;LD衰减的速度在不同物种间或同物种的不同亚群间差异非常大。所以,通常会使用“LD衰减距离”来描述LD衰减速度的快慢。 衰减距离,是当平均LD系数衰减到一定大小的时候,对应的物理距离。(这个一定大小是没有特别规定的,比如可以到一半大小,可以到0.5,可以到0.1) 上面的图描绘了不同水稻群体的LD衰减曲线,横坐标是物理距离(kb),纵坐标是LD系数(r^2)。 LD衰减曲线就是利用曲线图来呈现基因组上分子标记间的平均LD系数随着标记间距离增加而降低的过程。大概的计算原理就是先统计基因组上两两标记间的LD系数大小,再按照标记间的距离对LD系数进行分类,最终计算出一定距离的分子标记间的平均LD系数大小。 我们看左边的图,Japonic这个亚群(红色的线)在基因组100Kb距离的平均LD系数大小为0.35,到了200kb距离,对应的LD系数降低到了不到0.3。LD衰减速度在不同亚群是不一样,Japonica衰减速度最慢(其他亚群在物理距离很小的时候LD系数降就从0.35衰减到了0.3),衰减距离最大。 知道这个衰减速度快慢和衰减距离有什么用呢? LD衰减速度越慢,形成的单体型区块越大,关联分析中需要的群体和标记数目越少,但定位越不精准。此外,同一个连锁群上,LD衰减地慢说明该群体受到选择,一般来说,野生群体比驯化改良群体LD衰减快,遗传多样性高。而驯化选择会导致群体遗传多样性下降,位点间的相关性(连锁程度)加强。所以,驯化程度越高,选择强度越大的群体,LD衰减速度越慢。 也就是说左边这个图的4个水稻品种中,Japonic是驯化程度最高的。 LD衰减距离应用: 辅助分析进化和选择,同一个连锁群上,LD衰减地慢说明该群体受到选择。 一般而言,两个位点在基因组上离得越近,相关性就越强,LD系数就越大。反之,LD系数越小。也就是说,随着位点间的距离不断增加,LD系数通常情况下会慢慢下降。 一般而言,LD系数大于0.8就是强相关。如果LD系数小于0.1,则可以认为没有相关性。如果LD衰减到0.1这么大的区间内都没有标记覆盖的话,即使这个区间有一个效应很强的功能突变,也是检测不到关联信号的。所以,通常可以通过比较LD衰减(到0.1)距离和标记间的平均距离,来判断标记是否对全基因组有足够的覆盖度。 如果GWAS检测到显著关联的区间后,则可以通过进一步绘制局部的LD单倍型块图(这个后面讲),来进一步判断显著相关的SNP和目标基因间是否存在强LD关系。 基因组上那些受选择的区域相比普通的区域,LD衰减速度也是更慢的。 判断GWAS所需标记量,决定GWAS的检测效力以及精度; GWAS标记量 = 基因组大小 / LD衰减距离。 4.2 单倍型块(Haplotype Block)描述LD不平衡的另一个常用的图就是LD单倍型块图。 单倍型块,即连锁不平衡区域,是指同一条染色体上处于连锁不平衡状态的一段连续的区域。单倍型块分析可以用于筛选tag SNP、确定候选基因的范围等。 颜色从白色到红色,代表连锁程度从低到高,方框中的数值为LD系数r^2,为了美观,这里将 r2^2乘以了100。 上面的图中每个正方形是两个相邻SNP的LD值计算结果,如果大于设定的阈值(这里估计是大于94),那么就构成一个block,图中黑框为一个block,共有三个。可以从三个block中分别选择一个SNP作为Tag SNP来分别代表这三个block。 5. 全基因组关联分析(GWAS)全基因组关联分析(Genome-Wide Association Study, GWAS),以连锁不平衡(linkage disequilibrium, LD)为基础,通过分析数百个或者数千个个体的高密度分子标记的分离特征(一般是上万个或者上百万个SNP标记),筛选与复杂性状表现型变异相关联的分子标记,进而分析分子标记对表现型的遗传效应。 插一句题外话,GWAS中最后一个字母已经是study,在写英文文章的时候不要写成GWAS study…… 传统的QTL定位研究通常以2个亲本杂交群体为研究对象, 通过连锁作图定位目标性状位点。局限性是投入大量资源构建数量庞大的重组群体。而关联分析则可以利用个体在全基因组范围的遗传变异标记进行多态性检测,获得更高分辨率的定位结果(单碱基水平), 遗传变异来源也更为广泛,根据统计量或者显著性P值筛选最有可能影响该性状的遗传变异,往往能定位到比双亲本作图群体中更多的性状关联位点(这可节省太多时间了)。 GWAS的局限性在于可以确定相关位点但不能直接确定基因本身,假阳性也比较高。解决这一局限性有两种策略,一是算法,二是群体的选取。 5.1 GWAS算法模型目前用的算法模型有以下几种: 1.GLM (Generalized linear model;一般线性模型) 2.MLM (Mixed linear model;混合线性模型) 3.GEMMA、CMLM、SUPER、FarmCPU、Blink 主要介绍前两个,GLM模型以群体结构矩阵 Q或主成分分析矩阵PCs为协变量做回归拟合。 如果两个表型差异很大,但群体本身还含有其他的遗传差异(如地域等),则那些与该表型无关的遗传差异也会影响到相关性。MLM模型可以把群体结构矩阵 Q、亲缘关系矩阵(kinship)或联合利用主成分分析矩阵PCs和亲缘关系矩阵为协变量,把这种位点校正掉(也就是抑制假关联)。 其他模型大多是基于GLM和MLM进行优化,比如GEMMA 计算个体基因型相似性矩阵,排除了LD的干扰。其他暂时就不多说了,感兴趣再深入研究研究。 5.2 GWAS群体选取群体中丰富的表型变异和充分的遗传重组是GWAS 成功的关键条件,有以下选取策略: (1) 群体内没有明显的群体结构,样本间没有过近的亲缘关系,同时具有丰富的表型变异 (2) 群体来自具有一定水平遗传分化的不同类群(如不同亚种与亚群),具有丰富的遗传和表型变异,但同时不同类群之间存在频繁的遗传交流,保证目标性状在不同类群内部也存在一定水平的变异 目前GWAS样本量普遍大于100份 前面也说过,同时要考虑GWAS标记量,计算公式GWAS标记量 = 基因组大小 / LD衰减距离。 在用GATK做基因分型的时候也要注意,结果一般保留maf值大于0.05的 SNP,通常认为这是在驯化选择区间内。 5.3 GWAS结果图解还是先说明一下,做GWAS需要有三类数据:SNP数据、表型数据和群体结构。这些数据在实操会再次提到。 GWAS的结果通常以曼哈顿图和QQ图来展示。曼哈顿图显示每个SNP在关联分析中的显著性水平; QQ 图反映关联分析的效果。 以下图721份水稻GWAS结果图为例: 5.3.1 曼哈顿图(Manhattan Plot)图4(A)就是一个曼哈顿图,每个点代表一个SNP,x轴代表SNP在基因组上的遗传位置,y轴为–log10 (P-value),显著性水平线有红蓝两条(P = 0.01和P= 0.05)。y轴值越大,说明该位点表型的关联程度越大,受到连锁不平衡(LD)的影响,基因组上强关联位点周围的SNP也会呈现出关联性由高到低连续变化的信号强度,从而在P值小的地方出现尖峰(peak)。 上面文章深入分析水稻6号染色体上与抽穗期相关的一个尖峰,将其定位在Hd3a附近, 估计候选区间大概在 2.68–4.62 Mb (图4C)。这个候选区间是如何确定的呢? 我们通常将显著关联SNP在N kb以内的位置确认为相关区间,这个N就是对应的LD衰减距离。确定GWAS鉴定的候选区间后,就可以在候选区间内找到基因功能注释和表型相关的基因,或者有其他组学或者功能研究支持的基因进行验证和深入研究。 5.3.2 QQ图(QQ Plot)图4(B)是QQ图,本质上就是做两组数据的比较,判断是否一致。每个点代表一个SNP,横坐标是期望的P-value,纵坐标为实际观测到的P-value,虚线代表实际观测和期望的P-value值一致的情况。 我们做关联分析,当然是希望大部分的位点观测值和期望值一样(在对角线,也就是这里的虚线上),也就是这部分位点确定是与性状不关联的。一部分位点在虚线的上方,也就是观测值超过期望值,说明这些位点的效应超过随机效应,也就是这些位点是与性状显著相关的。也就是出现4(B)这个图才是理想的结果。 还有以下三种其他情况: 1 观察值低于期望值(点在对角线下方),可能是模型不合理,P-value被过度矫正。或者是群体中大量SNP位点间存在连锁不平衡,有效位点数(相互间不存在连锁不平衡的位点)明显低于实际位点数,所以P-value的期望值被低估了。 2 观察值和期望值相同,说明没有找到与性状显著关联的位点。 3 观察值显著高于期望值(点全都在对角线上方),也就是所有位点都与某个性状显著相关,说明分析模型不合理,假阳性太多了。 现在基于GWAS分析还衍生出其他诸如TWAS(基因表达量做标记)、PWAS(蛋白做标记)、EWAS(甲基化表观信息做标记)等关联分析方法,做标记的信息不同,本质上也都是和表型做关联分析。 6. 泛基因组(Pan-Genome)想了想还是把泛基因组也放了进来,虽然泛基因组研究方法和上面的研究方法不一样,但也是群体基因组学的一个重要分支,主要是开展群体基因组重测序和遗传变异的分析工作。 我们知道单一的参考基因组仅能代表物种基因组空间范围的一小部分(同一个物种不同亚群基因组存在差异),以线性参考基因组进行群体变异的分析就会错失一些变异位点(SV&CNV&PAV)。所以这个时候我们就需要获取一个物种的全部遗传信息(只能说是相对的全部),并解析每个个体间的遗传变异,这就诞生了泛基因组(Pan-Genome)研究。 6.1 核心基因/非核心基因在泛基因组中有两个非常重要的概念: 1 核心基因(core genome):在物种所有个体内都存在的,保持生命体基本功能,代表物种基本表型特征。 2 非必需基因(dispensable genome):使不同个体在表型,生活方式、代谢等多方面发生明显的分化,最终表现为丰富的遗传多样性。 上图可以看出核心基因和非必需基因的区别,以及最后融合成pan-genome。主要注意一点,核心基因是这个物种中所有个体都存在的,但不一定是必须基因,必须基因的概念比核心基因窄,核心基因包含了必须基因。 现在的pan-genome分析的文章一般还会对基因集做更进一步的细分: core 核心基因,所有样本共享 softcore 次核心基因,90%样本共享 dispensable 非必需基因,多个样本共享但 < 90% private 特有基因,1%,或者仅存在于一个样本中 对不类型基因集进行保守性分析,有助于挖掘适应性进化或驯化中发挥关键作用的基因。文章中用的比较多的还有核心基因/非必需基因与重复序列相关性分析、表达水平分析等等,这些展开来说太多了,具体文章具体分析。 6.2 泛基因组组装泛基因组组装方式现在见的比较多的是两种: 6.2.1 基于二代测序(迭代组装) How the pan-genome is changing crop genomics and improvement | Genome Biology | Full Text (biomedcentral.com) 将多个样本二代下机数据与参考基因组比对,未比对上的reads组装成新的contigs,将这些contigs添加到原始参考序列中(一般直接放在最后),最终获的物种的泛基因图谱。 看得出来这种方式简单粗暴,可以快速得到泛基因组信息(也省钱),但是有二代数据的通病——如果物种的基因组比较大,或者测序深度不够深的话,组装的contigs连续性会比较差。 6.2.2 基于三代测序(从头组装&图形泛基因组) Plant pan-genomes are the new reference | Nature Plants 对多个个体进行从头组装、注释,从全基因组层面识别变异信息,也是现在用的最广泛的方法。不依赖参考基因组,但是需要较高的测序深度(保证准确性),组装到染色体水平。图形泛基因组是在从头组装的基础上,基于图论的组装方法,利用有向图将物种基因组分为核心基因组和非必需基因组。 6.3 泛基因组分析主要可以分以下两部分: 这部分内容留到以后我做泛基因组组装实操再做详细解释和补充(先挖个坑)。 下篇笔记将简单实操一下GWAS分析,使用软件为TASSEL。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"群体基因组学","slug":"群体基因组学","permalink":"http://www.shelven.com/categories/%E7%BE%A4%E4%BD%93%E5%9F%BA%E5%9B%A0%E7%BB%84%E5%AD%A6/"}],"tags":[{"name":"群体基因组学","slug":"群体基因组学","permalink":"http://www.shelven.com/tags/%E7%BE%A4%E4%BD%93%E5%9F%BA%E5%9B%A0%E7%BB%84%E5%AD%A6/"}]},{"title":"三维基因组学——如何用Hi-C数据分析拓扑关联结构域","slug":"三维基因组学——如何用Hi-C数据分析拓扑关联结构域","date":"2023-06-14T16:24:17.000Z","updated":"2023-06-16T08:49:30.000Z","comments":true,"path":"2023/06/15/a.html","link":"","permalink":"http://www.shelven.com/2023/06/15/a.html","excerpt":"本篇笔记主要记录三维基因组学的学习,以及演示如何利用Hi-C数据分析Compartment和拓扑关联结构域(TAD),所使用的分析Hi-C数据的软件为HiC-Pro,可视化软件为HiCPlotter和R包HiTC。","text":"本篇笔记主要记录三维基因组学的学习,以及演示如何利用Hi-C数据分析Compartment和拓扑关联结构域(TAD),所使用的分析Hi-C数据的软件为HiC-Pro,可视化软件为HiCPlotter和R包HiTC。 1. 三维基因组简介首先什么是三维基因组学?三维基因组学(Three-Dimensional Genomics, 3D Genomics),是指在考虑基因组序列、基因结构及其调控元件的同时,对基因组序列在细胞核内的三维空间结构,及其对基因转录、复制、修复和调控等生物过程中功能的研究。 随着基因组学和表观基因组学的发展,基因组学研究的维度经历了从一维到三维的转变: 一维:基因组序列的测定与组装 二维:调控序列/转录因子—基因互作网络、染色质状态、表观修饰 三维:基因组的三维空间结构 基因组不是简单的线性序列,是有三维空间结构的,而且这种三维空间结构可以对DNA辅助、基因转录调控、染色质浓缩分离等生物学过程产生重要的影响。 我们知道,真核生物的染色质结构由低级到高级可以分为4种: 一级结构:一系列核小体相互连接成的念珠状结构 二级结构:由核小体连接起来的纤维状结构经螺旋化形成中空的螺线管 三级结构:由螺线管螺旋化形成的筒状结构,称为超螺线管 四级结构:超螺线管进一步螺旋折叠形成染色单体 三维基因组的层级结构类似于真核生物的染色质结构,在不同的分辨率下也可以划分为4个层级结构: 染色质环(Chromatin loops),分辨率:<1 Kb - few Mb:染色质在空间中形成的环状结构,使相距很远的染色质区域在三维空间中可以聚集在一起。 拓扑关联结构域(Topologically associating Domains, TAD),分辨率:40 Kb - 3 Mb:相互作用相对频繁的染色质区域。 染色质区室(A/B compartments),分辨率:1 - 100 Mb:A compartments:开放的染色质、表达活跃、基因丰富、较高的GC含量、激活型的组蛋白标记,通常位于细胞核内部;B compartments:关闭的染色质、表达不活跃、基因缺乏、结构紧凑、沉默基因的组蛋白标记,通常位于细胞核外围。 染色质疆域(Chromosome Territory, CT),分辨率:~100 Mb:在真核生物中,细胞核内染色质分布并不是随机的,为了跨越较大距离实现相互作用,这些染色质会在三维空间中靠的更近,这就是染色质疆域。 不同层次分辨率下的研究重点不同,比如最小的loop层次对应的是基因级别甚至更小的元件互作;10kb级别一般就可以鉴定TAD之间的互作关系(也是研究比较多的);更大一些的染色质区室也是研究比较多的内容;在染色质疆域这个层次主要就是研究染色体之间的互作关系了。相应的,分辨率越高,需要的有效互作数据量也越大。 2. 三维基因组技术目前三维基因组结构的检测时基于染色体构象捕获技术,也就是3C(chromosome conformation capture)技术,3C是所有染色体构象捕获技术的基础。根据染色质的互作类型,3C技术又可以大致分为5种方法: 1 versus 1:3C的由来,经过酶切、DNA片段绑定、反向交联后,通过qPCR确认互作位点。 1 versus Many/All:4C技术,3C技术的升级版。反向交联前和3C一样,经过了第二轮消化和绑定,通过特定位点的反向PCR检测特定位点与全基因组潜在位点互作情况。 Many versus Many:5C技术,基于3C的另一升级版。5C技术的通量增大。 Many versus All:Capture-C技术,最大不同是在互作片段对的捕捉技术上,它是利用生物素标记反向互补到限制酶酶切位点,从而进行对所有感兴趣的互作位点和全基因组位点之间互作对之间的捕捉。 All versus All 其中“1”“Many”以及“All”代表的是在一次实验中所涉及的位点,比如说 “1 versus All” 的意义就是该次实验调查的是一个位点和全基因组中所有可能潜在互作位点之间的互作情况。 近年来all versus All也就是检测全局的互作技术发展比较快,有以下一些技术应用: Hi-C技术。近年最火的全基因组 3D 基因组测序技术,不仅可以用于检测全基因组的三维基因组结构和染色质互作,同时还可以用于辅助基因组组装(同样用的很多,现在组参考基因组都要测HiC,以组装到染色体水平)等。 ChIA-PET技术( chromatin interaction analysis paired-end tag sequencing):与3C基础上发展的其他技术不同,区别在于第一步对于互作位点的 DNA 破碎不是通过限制酶进行消化,而是利用超声波击碎。然后应用抗体对特定蛋白参与的互作区段进行富集,并对这些互作区段进行消化连接,提取含有接头的双端序列( paired end tag, PET)进行互作检测。 DLO Hi-C技术( digestion-ligation-only Hi-C):该技术相对于传统的全基因组染色体构象捕获技术 Hi-C 而言更加高效简单,仅需要两轮的消化连接过程,无须生物素标记,未连接的 DNA 也可以被有效地去除,极大地提高了染色体构想捕获效率。 DLO Hi-C 技术更像是 Hi-C 技术的一个升级优化。 原位Hi-C (in situ Hi-C):使用完整的细胞核进行后续反应,减少了Hi-C中随机连接造成的背景噪音。使用四碱基酶Mbol进行酶切,提高了分辨率,实验时间由Hi-C的7天缩短至3天。 HiChIP (in situ Hi-C followed by chromatin immunoprecipitation):该技术是一种利用原位Hi-C原理和转座酶介导构建文库来解析染色质构象的方法。 3. 利用Hi-C数据分析拓扑关联结构域(TAD)现在Hi-C测序是应用最广泛的三维基因组学技术,对Hi-C数据的处理,构建Hi-C互作图谱和鉴定TAD结构域是最关键的问题,也发展了一大批专门的生物信息学算法软件,我这里用经典的软件HiC-Pro、HiCPlotter和R包HiTC跑一遍Hi-C数据分析的流程。 为了快速得到结果,这里选择了Gossypium hirsutum四倍体陆地棉的两条染色体参考序列,和一部分Hi-C测序结果(50万条reads的双端测序结果)跑下流程。后续制作Hi-C互作矩阵,和分析Compartment和TAD的.bed后缀文件和.matrix后缀文件来自尤师姐(前面的这点数据做不出互作图,太少了,跑完所有数据又很慢……)。 两个软件安装过程就不说了,可以参考github官方,如果HiC-Pro很难安装依赖的话可以用官方提供的singularity镜像: nservant/HiC-Pro: HiC-Pro: An optimized and flexible pipeline for Hi-C data processing (github.com) 当前路径文件结构如下: 3.1 获得染色质互作矩阵(HiC-Pro)12345678# 1.准备酶切片段## dpnii是选择的限制性核酸内切酶,可以用别的,查看digest_genome.py源码python digest_genome.py Gh_genome.fa –r dpnii –o Gh_dpnii.bed# 2.统计酶切片段大小awk ’{print $3-$2;}’ Gh_dpnii.bed > Gh_dpnii_size.txt# 3.构建参考基因组索引,生成基因组大小的文件samtools faidx Gh_genome.faawk ‘{print $1”\\t”$2;}’ Gh_genome.fa.fai > Gh_size.txt 这里酶切参考基因组生成的bed文件如下所示: 123456789101112131415161718192021222324252627282930313233343536373839404142# 4.修改config文件## 主要修改基因组索引文件路径、基因组大小文件路径、酶切片段文件路径和分辨率## BOWTIE2_IDX_PATH、GENOME_SIZE、GENOME_FRAGMENT、BIN_SIZEvim config.txt######################################################################### Alignment options#######################################################################FORMAT = phred33MIN_MAPQ = 0BOWTIE2_IDX_PATH = /home/Bioinfor/Gh_indexBOWTIE2_GLOBAL_OPTIONS = --very-sensitive -L 30 --score-min L,-0.6,-0.2 --end-to-end --reorderBOWTIE2_LOCAL_OPTIONS = --very-sensitive -L 20 --score-min L,-0.6,-0.2 --end-to-end --reorder######################################################################### Annotation files#######################################################################REFERENCE_GENOME = GhGENOME_SIZE = /home/Bioinfor/Gh_size.txt######################################################################### Digestion Hi-C#######################################################################GENOME_FRAGMENT = /home/Bioinfor/Gh_dpnii.bedLIGATION_SITE = AAGCTAGCTTMIN_FRAG_SIZE = 100MAX_FRAG_SIZE = 160000MIN_INSERT_SIZE = 200MAX_INSERT_SIZE = 600######################################################################### Contact Maps#######################################################################BIN_SIZE = 5000 20000 100000MATRIX_FORMAT = upper 主要是修改以上内容,但也要注意下Hi-C双端测序文件的后缀,要让软件能匹配上。 12# 5.运行HiC-Pro,获得染色质互作矩阵HiC-Pro -c config.txt -i Gh/ -o HiC_Pro_out HiC-Pro的主要结果放置在目录HiC_Pro_out/ hic_results/matrix/Gh/下,为5000、20000、100000 分辨率下的_abs.bed 以及_iced.matrix后缀文件。其他结果文件和分析图片可以在hic_results文件夹里查看,这里不展示了。 主要展示后续用的文件,以下为Gh_5000_abs.bed文件: 这个文件将染色体划分为5000 bp的bin,并且在第四列进行编号。 以下为Gh_5000_iced.matrix文件: 这个文件第一列和第二列都是bin编号,第三列为两个bin之间归一化之后的互作强度。 3.2 绘制染色质互作图(HiCPlotter)这里使用的是尤师姐提供的跑完了A01染色体的Gh_20000_iced.matrix与Gh_20000_abs.bed文件(我的示例Hi-C数据量太少)。 1234567891011# HiCPlotter绘制染色体互作图python HiCPlotter.py -f Gh_20000_iced.matrix -o Ghir_A01 -r 20000 -tri 1 -bed Gh_20000_abs.bed -n Ghir_A01 -chr Ghir_A01## HiCPlotter绘制染色质互作几个常用参数:-chr 染色体名称,如果染色体名称不是Chr*,-chr参数需传入对应的值-o 输出文件名-tri 默认值为0,1代表着传入的matrix和bed文件是HiC-Pro运行的结果-r 分辨率-n 任务名# ZModem协议传输文件sz Ghir_A01-Ghir_A01.ofBins\\(0-5887\\).20K.png 染色体Ghir_A01内部的互作图Ghir_A01-Ghir_A01.ofBins(0-5887).20K.png如下所示。 3.3 鉴定TAD(HiCPlotter)这里使用的数据与3.2一样。 12345678910# HiCPlotter鉴定TADpython HiCPlotter.py -f Gh_20000_iced.matrix –bed Gh_20000_abs.bed –o TAD -r 20000 -n Ghir_A01 -chr Ghir_A01 -tri 1 -fh 0 -s 600 -e 900 -ptd 1 -pi 1## HiCPlotter绘制TAD几个常用参数:-ptd 默认0,输入1,调用绘制TAD算法-fh 输入文件中抬头需要删除的行数,没有header lines就是0-s 起始位点(第几个bin)-e 结束位点(第几个bin)# ZModem协议传输文件sz TAD-Ghir_A01.ofBins\\(600-900\\).20K.png 染色体Ghir_A01的12Mb到18Mb之间TAD鉴定结果图TAD-Ghir_A01.ofBins(600-900).20K.png如下: 3.4 鉴定染色质区室和TAD(HiTC包)这里使用的数据来自尤师姐提供的LG02_50000_abs.bed与LG02_50000_iced.matrix文件,也是HiC-Pro跑出来的结果文件。 因为用到了R包,集群R有点问题,我暂时传到本地用自己电脑跑了下: 12345678910111213141516# 下载和加载HiTC包> BiocManager::install("HiTC")> setwd("D:/zhuomian/D5_50Kb/D5_50Kb")> library(HiTC)# 导入数据(importC函数读取HiC-pro的结果)> mydata <- importC("D:/zhuomian/D5_50Kb/D5_50Kb/LG02_50000_iced.matrix", xgi.bed = "D:/zhuomian/D5_50Kb/D5_50Kb/LG02_50000_abs.bed")# 主成分分析和鉴定Compartment> pc <- pca.hic(mydata$LG02LG02, npc = 1, asGRangesList = TRUE)> write.csv(pc, file = "D:/zhuomian/D5_50Kb/D5_50Kb/LG02_50k.csv")> mydata2 <- read.table("D:/zhuomian/D5_50Kb/D5_50Kb/LG03_50k.csv",header = TRUE,sep = ",")> barplot(mydata2$score)# 热图展示> mapC(mydata$LG02LG02, trim.range = 0.94, col.pos = c("white", "red")) 这里主成分分析得到的LG02_50k.csv,统计第一主成分score做条形图就可以鉴定染色质区室Compartment A/B了,以下链接是提出者的文章。 https://www.ncbi.nlm.nih.gov/pmc/articles/PMC2858594/ 跑出来的互作热图如下: 也可以选取染色体的一部分(一般是自己感兴趣的部分),做热图: 12> data1 <- extractRegion(mydata$LG02LG02,chr = "LG02", from = 1000, to = 5000000)> mapC(data1, trim.range = 0.94,col.pos = c("white", "red")) 关于如何用HiTC包分析Hi-C数据,也可以看bioconductor的一篇文章: Analyzing Hi-C data with the HiTC BioC package (bioconductor.org)","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"三维基因组学","slug":"三维基因组学","permalink":"http://www.shelven.com/categories/%E4%B8%89%E7%BB%B4%E5%9F%BA%E5%9B%A0%E7%BB%84%E5%AD%A6/"}],"tags":[{"name":"HiC-Pro","slug":"HiC-Pro","permalink":"http://www.shelven.com/tags/HiC-Pro/"},{"name":"HiCPlotter","slug":"HiCPlotter","permalink":"http://www.shelven.com/tags/HiCPlotter/"},{"name":"HiTC","slug":"HiTC","permalink":"http://www.shelven.com/tags/HiTC/"}]},{"title":"单细胞转录组分析","slug":"单细胞转录组分析","date":"2023-06-13T14:06:51.000Z","updated":"2023-06-16T08:50:07.000Z","comments":true,"path":"2023/06/13/a.html","link":"","permalink":"http://www.shelven.com/2023/06/13/a.html","excerpt":"最近因为学校网络信息中心升级防火墙,导致集群无法访问公网,我搭建的反向代理服务器也暂时无法使用(真的想吐槽学校的网络管理员,三个星期了还没能解决集群的网络问题)……近期要进行中期答辩,先用向日葵远程一下实验室闲置的电脑应急,顺便把最近的学习笔记补上。 这篇笔记主要记录下单细胞组学的学习,以及单细胞转录组(Single-cell RNA-sequencing,scRNA-seq)的分析流程。","text":"最近因为学校网络信息中心升级防火墙,导致集群无法访问公网,我搭建的反向代理服务器也暂时无法使用(真的想吐槽学校的网络管理员,三个星期了还没能解决集群的网络问题)……近期要进行中期答辩,先用向日葵远程一下实验室闲置的电脑应急,顺便把最近的学习笔记补上。 这篇笔记主要记录下单细胞组学的学习,以及单细胞转录组(Single-cell RNA-sequencing,scRNA-seq)的分析流程。 1. 单细胞测序发展我们做植物高通量测序做的最多的是RNA-seq,比较不同组织、不同时期或者不同处理下同一个组织基因的差异表达。我们做RNA-seq是建立在对一个组织的细胞进行测序的基础上,而组织是几类细胞的集合,因此我们得到的基因表达量是所有细胞的平均值。 单细胞测序可以在单个细胞的水平上构建细胞图谱,让我们更深入了解植物组织的细胞类型,获取每个细胞的转录本信息,研究细胞的发育动态(比如细胞如何进行分化)等等。 Nature杂志每年都会总结每个领域最有价值的年度技术,2018年是单细胞转录组技术,2019年是单细胞多组学技术,2020年是空间转录组技术,可以看出带有单细胞和空间分辨率转录组技术应用的重要性。 上图是单细胞技术在植物中的研究历程,最后一个2022年的Stereo-seq也就是华大自研的时空组学技术,据华大发表在cell上的文章Spatiotemporal transcriptomic atlas of mouse organogenesis using DNA nanoball-patterned arrays描述,Stereo-seq的技术参数优于当前其他空间转录组技术,对空间异质性的描述更为灵敏和直观。 对于Stereo-seq我个人的理解是补上了部分空间转录组测序无法做到单个细胞分辨率的短板,真正做到对动植物的组织或者器官中的所有细胞进行转录组信息分析,鉴定不同类型细胞的差异基因表达,构建空间图谱来分辨不同细胞的发育轨迹。感兴趣可以看下面华大的这篇scStereo-seq技术用于拟南芥的文章:The single-cell stereo-seq reveals region-specific cell subtypes and transcriptome profiling in Arabidopsis leaves - ScienceDirect 2. 单细胞测序平台 10x Genomics Chromium 微流控芯片技术获得单细胞反应体系,并在传统文库构建的基础上引入标签,通过追溯标签序列将众多mRNA、表面蛋白信息定位回原来的单个细胞。捕获效率最高达65% BD Rhapsody™ Single-Cell Analysis System 能为单细胞中每个转录本标记特异性分子标签,实现单细胞水平上基因表达谱的绝对定量。捕获效率最高达80% Illumina® Bio-Rad® Single-Cell Sequencing Solution 捕获效率低,仅为3%,但测序成本相对较低 ICELL8 Single-Cell System 捕获效率为30%,成本相对较低 C1™ 单细胞全自动制备系统 通量低、成本高、周期慢、对试验人员要求高,操作较繁琐和困难 3. 单细胞测序分析流程 质控部分和RNA-seq没有区别,去掉低质量的读序和测序接头。10X Genomics平台测的单细胞数据需要通过Cell Ranger回比到参考基因组,示例用法如下: 12345678 cellranger count --id=sample345 --transcriptome=/opt/refdata-cellranger-GRCh38-3.0.0 --fastqs=/home/jdoe/runs/HAWT7ADXX/outs/fastq_path --sample=mysample# --id 文件名# --transcriptome 参考基因组# --fastqs 转录组数据所在的路径# --sample 指定要使用的样本 这里主要记录下细胞过滤到亚群定义和标记基因筛选所要用到的软件Seurat4。因为我自己没有这方面的实验数据,仅仅只是尝试跑下流程,以下分析流程和代码来自: 单细胞转录组|Seurat 4.0 使用指南 - 知乎 (zhihu.com) 《Seurat 4 R包源码解析》 总目录 | 单细胞转录组分析标准流程 - 知乎 (zhihu.com) 示例数据来自10X Genomics免费提供的外周血单核细胞(PBMC)数据集:https://cf.10xgenomics.com/samples/cell/pbmc3k/pbmc3k_filtered_gene_bc_matrices.tar.gz 将示例数据上传至集群,解压,得到如下文件: 123├── barcodes.tsv(10X Genomics测序的与细胞相关的barcode信息,用于标识细胞)├── genes.tsv(基因信息,包括了基因组数据库人类登记号和基因名称)└── matrix.mtx(基因表达量矩阵) 这三个文件实际上要通过软件Cell Ranger对10X Genomics单细胞测序的下机数据进行比对基因组,统计捕获的细胞数、测序量得到。详细可以参考官方Cell Ranger软件的用法和结果分析,这里用的是官方整理后的数据,只进行下一步Seurat分析的演示。 以所在目录为工作目录,在linux中键入R进入交互命令行,运行Seurat。 3.1 导入数据和初步过滤123456789101112# 安装和加载Seurat和dplyr(用于数据清洗)R包BiocManager::install("Seurat") BiocManager::install("dplyr")library(dplyr)library(Seurat)# 读入数据并初步筛选pbmc.data <- Read10X(data.dir="/path/to/yourfile")# 创建Seurat对象,过滤检测少于200个基因的细胞,和少于3个细胞检测出的基因pbmc <- CreateSeuratObject(counts = pbmc.data, project = "pbmc3k", min.cells = 3, min.features = 200)pbmc 可以看到初步过滤后,现在的一个数据集包含了2700个细胞的13714个基因。 3.2 细胞过滤123456789101112131415161718# 新增一列percent.mt用于统计映射到线粒体基因组的reads百分比pbmc[["percent.mt"]] <- PercentageFeatureSet(pbmc, pattern = "^MT-")# 绘制细胞的三种特征,nFeature_RNA(每个细胞测到的特异基因数目)、nCount_RNA(每个细胞测到所有基因的表达量之和)、percent.mt的小提琴图png("fig01_cell_feature.png")VlnPlot(pbmc, features = c("nFeature_RNA", "nCount_RNA", "percent.mt"), ncol = 3)dev.off()# 细胞特征间的相关性绘图png("fig02_cell_feature_correlation.png", width=1000, height=400) plot1 <- FeatureScatter(pbmc, feature1 = "nCount_RNA", feature2 = "percent.mt")plot2 <- FeatureScatter(pbmc, feature1 = "nCount_RNA", feature2 = "nFeature_RNA")CombinePlots(plots = list(plot1, plot2))dev.off()# 根据特殊基因的数目以及线粒体基因比例过滤细胞,这里选取200 < nFeature_RNA < 2500 和percent.mt < 5的数据pbmc <- subset(pbmc, subset = nFeature_RNA > 200 & nFeature_RNA < 2500 & percent.mt < 5) pbmc fig01_cell_feature.png如下所示: 这里可以看到有的细胞检测到的特异基因数特别多,可能是因为多个细胞被同时捕获了,而特异基因数特别少的可能是低质量细胞。而线粒体基因比例特别高的细胞,有可能是一些快要死亡的细胞。这些低质量的细胞需要在这一步被过滤。 fig02_cell_feature_correlation.png如下所示: 这两个图分别表示了每个细胞测到所有基因的表达量之和与线粒体基因组百分比呈负相关,每个细胞测到所有基因的表达量之和与每个细胞测到的特异基因数目呈正相关。 这一步过滤后数据集剩下2638个细胞,共13714个基因。 3.3 细胞群体聚类1234567891011121314151617# 表达水平标准化## normalization.method 标准化方法,默认为“LogNormalize”## scale.factor 比例因子,用以计算标准化值,默认为“10000”pbmc <- NormalizeData(pbmc, normalization.method = "LogNormalize", scale.factor = 10000)# 鉴定mark基因(也就是特征基因)## selection.method mark基因筛选方法,“vst”为通过方差与均值比进行筛选## nfeatures 要筛选的mark基因的数目pbmc <- FindVariableFeatures(pbmc, selection.method = "vst", nfeatures = 2000)top10 <- head(VariableFeatures(pbmc), 10)# 展示mark基因png("fig03_10marker_genes.png",width=1000,height=400)plot1 <- VariableFeaturePlot(pbmc)plot2 <- LabelPoints(plot = plot1, points = top10, repel = TRUE)CombinePlots(plots = list(plot1, plot2))dev.off() fig03_10marker_genes.png如下所示: 这里筛选出2000个mark基因用于后续的下游分析,并且展示区分能力最强的前10个mark基因 123456789101112# Mark基因权重标准化## ScaleData 缩放基因的表达,给予基因同等的权重,使高表达基因不占据主导地位all.genes <- rownames(pbmc)pbmc <- ScaleData(pbmc, features = all.genes) # PCA主成分分析## RunPCA 计算特征值,在细胞间具有高度表达差异的基因有助于区分不同类型的细胞## 采用DimPlot方法可视化pbmc <- RunPCA(pbmc, features = VariableFeatures(object = pbmc))png("fig04_PCA.png")DimPlot(pbmc, reduction = "pca")dev.off() fig04_PCA.png如下所示: 12345678910111213# 评估主成分维度## num.replicate 重采样次数## dims 维度范围pbmc <- JackStraw(pbmc, num.replicate = 100) pbmc <- ScoreJackStraw(pbmc, dims = 1:20)## 评估主成分维度的方法1png("fig05_PC_importance_01.png",width=700,height=400)JackStrawPlot(pbmc, dims = 1:15)dev.off()## 评估主成分维度的方法2png("fig05_PC_importance_02.png",width=700,height=400)ElbowPlot(pbmc)dev.off() R包 Seurat 函数 JackStraw 、ScoreJackStraw进行重采样测试,评估用以进行细胞聚类的主成分维度。 fig05_PC_importance_01.png如下所示: fig05_PC_importance_02.png如下所示: 上图是比较不同主成分(PC)的P值分布与均匀分布(虚线);显著的主成分具有较低的P值(位于虚线上方);似乎10-12个主成分后,主成分的重要性急剧下降。 下图是肘部法则以确定最佳主成分数量,9-10个主成分附近有个拐点,表明大部分真实信号是在前10个pc中捕获的。 所以这里选择10个主成分用于后续分析。 12345678# 细胞群体聚簇pbmc <- FindNeighbors(pbmc, dims = 1:10) pbmc <- FindClusters(pbmc, resolution = 0.5) ## 绘图pbmc <- RunUMAP(pbmc, dims = 1:10) png("fig06_cluster_UMAP.png",width=700,height=400) DimPlot(pbmc, reduction = "umap")dev.off() fig06_cluster_UMAP.png如下所示 可以看到,根据mark基因的表达,所有细胞最终被分为了8种类型。 做完以上分析之后,还可以根据自己的研究目标新型不同的下游分析,具体来说可以筛选亚群的标记基因(与其他类别细胞的差异表达基因)、进行细胞的发育轨迹分析、做不同品种间细胞类型的比较等等。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"单细胞转录组","slug":"单细胞转录组","permalink":"http://www.shelven.com/categories/%E5%8D%95%E7%BB%86%E8%83%9E%E8%BD%AC%E5%BD%95%E7%BB%84/"}],"tags":[{"name":"Seurat","slug":"Seurat","permalink":"http://www.shelven.com/tags/Seurat/"}]},{"title":"基因组共线性分析——MCScanX","slug":"基因组共线性分析——MCScanX","date":"2023-04-20T15:55:19.000Z","updated":"2023-05-08T13:55:51.000Z","comments":true,"path":"2023/04/20/a.html","link":"","permalink":"http://www.shelven.com/2023/04/20/a.html","excerpt":"因为自己的生信基础知识比较薄弱,最近在华农跟着王老师上了一些植物基因组课程,记录一下。这一次主要结合课堂内容、MCScanX官方文档以及自己的理解,演示基因组共线性工具MCScanX的用法。","text":"因为自己的生信基础知识比较薄弱,最近在华农跟着王老师上了一些植物基因组课程,记录一下。这一次主要结合课堂内容、MCScanX官方文档以及自己的理解,演示基因组共线性工具MCScanX的用法。 1. 共线性分析在樊龙江主编的《植物基因组学》中提到,植物起源于水生藻类,不同植物在基因水平上具有一定保守性(也就是具有一定的相同基因)。在一定的亲缘关系内,这种基因组水平上的保守性(基因组区块排列顺序保守性),也就是不同植物基因组间的共线性。 当我们拿到两个植物基因组数据,想要分析两个物种间是否存在基因进化历史、染色体结构变异、重要功能基因的插入缺失或者鉴定全基因组复制事件的时候,就可以利用一些基因组共线性分析工具进行直观的作图和分析。 做共线性分析之前需要区分两个描述基因组共线性的名词: Synteny:两个物种的一组基因位点,在每个物种中位于同一条染色体(顺序不一定相同)。 Collinearity:两个物种的一组基因位点,分别位于各自的同一条染色体上,并且顺序也是一致的。 这里染色体打了个重点,因为所有的共线性分析都是基于染色体水平的基因组进行比较的,只有contig数据分析的话意义不大。现在的共线性研究以及开发的工具一般用的是collinearity(包括后面要说的MCScanx)。 共线性分析的原理主要都是分为三步: 获得基因在染色体上的位置(如基因组注释得到的gff文件)。 将基因的CDS/蛋白序列进行比对,得到高度相似的基因对(Anchoring)。 鉴定相同基因排列顺序的共线性区块(Chaining),不同软件算法不一样,这里不讨论算法只讨论如何应用。 其他就不过多介绍了,接下来主要讲讲MCScanX软件的用法。 2. 下载和编译MCScanX按照官网克隆仓库,编译即可。MCScanX github官网 123git clone https://github.com/wyp1125/MCScanX.gitcd MCScanXmake 编译之后可以看到主文件的MCScanX、MCScanX_h和Duplicate_gene_classifier三个核心分析程序,以及downstream_analyses下游分析文件夹中的12个与下游分析有关的java和perl文件。 3. 运行MCScanX3.1 数据预处理MCScan支持的输入文件有两个: m8输出格式的BLASTP比对结果文件 记录染色体、基因名称以及起始和终止位点4个信息的gff文件 这里以棉花基因组和近缘物种可可基因组为例,两者都可以在NCBI上找到蛋白序列和注释的gff3文件。由于上机课中给的蛋白序列和gff文件都是处理好的,如果自己处理gff文件,总体思路是从gff文件的第3列提取’gene’关键词,从第9列分离基因名称信息,保留第1列染色体信息,保留第4列和第5列保留start和end信息即可。 这里输入的gff文件不是标准格式,简单写个python脚本处理: 1234567891011import osimport pandas as pdfile_path = './Theobroma_cacao.chr.gff3'with open(file_path, "r") as file: temp = pd.read_csv(file, sep = '\\t',comment = '#', header = None) # 制表符分隔,#号为注释标识,无列名 temp = temp.drop(temp[temp[2] != 'gene'].index) # 第3列不为gene的行的索引,drop()删除 temp = temp[[0,8,3,4]] temp[8] = temp[8].map(lambda x: x.split(';')[0].split('=')[1].split(':')[1]) # 对第9列的处理,只取geneid temp.to_csv('Tcacao.gff3', header = None, index = None, sep = '\\t') 当然,根据官网的表述,最好将染色体名称改为两个字母(物种缩写) + 数字(代表染色体编号)的形式。不改也没关系,两个基因组的染色体名字不要一样就好了,不改的话只有在下游分析对共线性区块分组的时候有点影响(添加的物种那列只显示两个字母)实质上不会有什么影响。 这步检查一下gene数量与pep文件的蛋白质条数是否一样,不一样的话可能是pep中有转录本蛋白序列,重新筛选完整的gff3文件,再用gffread提取蛋白序列,这里就不赘述了,如果处理有问题后续在更新。 本例中,棉花Gossypium herbaceum相关文件前缀为Gh;可可Theobroma cacao相关文件前缀为Tcacao。 12345678910# 合并蛋白序列(方便做基因组组内和组间共线性分析)cat Gh.pep Tcacao.pep > Gh_Tcacao.pep# blast建库makeblastdb -in /public/home/wlxie/biosoft/MCScanX/Data/ExerciseData/Gh_Tcacao.pep -dbtype prot -input_type fasta -out Gh_Tcacao# 蛋白序列拆分40份(方便提交并行任务到集群)perl fasta-splitter.pl --n-parts 40 Gh_Tcacao.pepmkdir Gh_Tcacaomv Gh_Tcacao.part* Gh_Tcacao 用到的蛋白序列拆分perl脚本来自于Kirill Kryukov,具体在哪个仓库找不到了……这里提供下载,拆分后文件夹名称如下: 上面拆分的40个蛋白序列名称数字部分是等宽的,不方便提交并行任务到集群,这里简单修改下文件名称。 12345678910111213141516171819import os # 获取目录下所有文件列表path = '/public/home/wlxie/biosoft/MCScanX/Data/ExerciseData/Gh_Tcacao/'fileList = os.listdir(path)# 设置待修改的前缀和后缀prefix = 'Gh_Tcacao_'suffix = '.pep'# 批量修改文件名m = 1for file in fileList: old_path = path + os.sep + file if os.path.isdir(old_path): continue new_path = path + os.sep + prefix + str(m) + suffix os.rename(old_path, new_path) m += 1 修改之后提交40个blastp并行任务,每个任务4核,很快就可以跑完。 1234567#!/bin/bash#SBATCH --array=1-40#SBATCH --cpus-per-task=4echo start on $(date)srun blastp -query Gh_Tcacao${SLURM_ARRAY_TASK_ID}.pep -out Gh_Tcacao_${SLURM_ARRAY_TASK_ID}.blast -db Gh_Tcacao -outfmt 6 -num_threads 4 -num_alignments 5 -evalue 1e-10echo end on $(date) 最后合并blastp结果。 12cat Gh_Tcacao_*.blast > Gh_Tcacao.blastrm Gh_Tcacao_*.blast 3.2 运行MCScanX和结果解读MCScanX有三个主命令: MCScanX共线性分析 MCScanX_h也是共线性分析,输入不是BLASTP文件,而是第三方检测的以制表符分隔的成对同源关系文件 Duplicate_gene_classifier使用MCScanX的算法鉴定singleton(单基因)和重复基因 这里用MCScanX,前面处理好了的blastp结果文件和gff文件放在同一个文件夹,输入的文件相对路径要到文件名的前缀部分 1./MCScanX Data/ExerciseData/Gh_Tcacao 结果文件如下: .collinearity 是两个基因组共线性结果文件(默认分析collinearity),包含了本次运行的参数、计算出的共线性区块等。还可以根据这个文件用grep的方法提取两个物种之间的同源基因(提取第二列第三列物种名字不一样的行,去重复,计数),这里也不赘述了。 .html 这个文件夹每个文件对应一条染色体共线性分析结果,包括基因座的共线性区块数量、基因名称(串联重复基因会标红)和具体比对上哪些共线性区块: .tandem文件显示基因组内所有串联重复基因对: 3.3 下游分析和作图官方在downstream_analyses文件夹中提供了12个下游分析的perl和java脚本。我个人将这个文件下的分析工具分为两类: 功能相关(此部分与数据处理相关) 12345678910111213141516# Detect_syntenic_tandem_arrays 检测串联重复基因../../../downstream_analyses/detect_collinear_tandem_arrays -g Gh_Tcacao.gff -b Gh_Tcacao.blast -c Gh_Tcacao.collinearity -o tandem_arrays# Dissect_multiple_alignment 将共线性区块分为物种内和物种间共线性区块../../../downstream_analyses/dissect_multiple_alignment -g Gh_Tcacao.gff -c Gh_Tcacao.collinearity -o dissect_multiple_alignment# add_ka_and_ks_to_collinearity.pl 计算ka和ks值(在collinearity结果后面增加两列,执行时间较长)cat Gh.cds Tcacao.cds > Gh_Tcacao.cds # 需要cds序列,且与collinearity文件中的序列名要一致,否则算出来全部都是-2perl ../../../downstream_analyses/add_ka_and_ks_to_collinearity.pl -i Gh_Tcacao.collinearity -d Gh_Tcacao.cds -o add_kaks_to_synteny# group_collinear_genes.pl 基因分组,构建基因家族(结果文件需要去掉第一行无效信息)perl ../../../downstream_analyses/group_collinear_genes.pl -i Gh_Tcacao.collinearity -o group_collinear_genes# 以及以下几种功能就不一一试了# detect_collinearity_within_gene_families.pl 检测基因家族的共线性基因对,需要用到前面构建的基因家族文件# origin_enrichment_analysis.pl 根据Duplicate_gene_classifier的结果识别输入基因家族的重复基因起源的潜在富集?不太懂什么意思 作图相关(此部分均有对应的配置文件,在downstream_analyses文件夹操作,其他文件路径java会发生未知错误) 1234567# dot_plotter 两组染色体所有共线性区块做点图java dot_plotter -g ../Data/ExerciseData/Gh_Tcacao/Gh_Tcacao.gff -s ../Data/ExerciseData/Gh_Tcacao/Gh_Tcacao.collinearity -c dot.ctl -o dot.png# dot.ctl配置文件内容如下800 //x轴维度(像素大小,下同,不再赘述)800 //y轴维度Ghir_A01,Ghir_A02,Ghir_A03,Ghir_A04,Ghir_A05,Ghir_A06,Ghir_A07,Ghir_A08,Ghir_A09,Ghir_A10,Ghir_A11,Ghir_A12,Ghir_A13 //x轴染色体名称Chromosome_1,Chromosome_2,Chromosome_3,Chromosome_4,Chromosome_5,Chromosome_6,Chromosome_7,Chromosome_8,Chromosome_9 //y轴染色体名称 1234567# dual_synteny_plotterjava dual_synteny_plotter -g ../Data/ExerciseData/Gh_Tcacao/Gh_Tcacao.gff -s ../Data/ExerciseData/Gh_Tcacao/Gh_Tcacao.collinearity -c dual_synteny.ctl -o dual_synteny.png# dual_synteny.ctl600 //图宽800 //图高Ghir_A01,Ghir_A02,Ghir_A03,Ghir_A04,Ghir_A05,Ghir_A06,Ghir_A07,Ghir_A08,Ghir_A09,Ghir_A10,Ghir_A11,Ghir_A12,Ghir_A13 //位于左边的染色体名称Chromosome_1,Chromosome_2,Chromosome_3,Chromosome_4,Chromosome_5,Chromosome_6,Chromosome_7,Chromosome_8,Chromosome_9 //位于右边的染色体名称 12345# circle_plotterjava circle_plotter -g ../Data/ExerciseData/Gh_Tcacao/Gh_Tcacao.gff -s ../Data/ExerciseData/Gh_Tcacao/Gh_Tcacao.collinearity -c circle.ctl -o circle.png# circle.ctl800 //图宽和高Ghir_A01,Ghir_A02,Ghir_A03,Ghir_A04,Ghir_A05,Ghir_A06,Ghir_A07,Ghir_A08,Ghir_A09,Ghir_A10,Ghir_A11,Ghir_A12,Ghir_A13,Chromosome_1,Chromosome_2,Chromosome_3,Chromosome_4,Chromosome_5,Chromosome_6,Chromosome_7,Chromosome_8,Chromosome_9 //环状图的染色体名称 1234567# bar_plotterjava bar_plotter -g ../Data/ExerciseData/Gh_Tcacao/Gh_Tcacao.gff -s ../Data/ExerciseData/Gh_Tcacao/Gh_Tcacao.collinearity -c bar.ctl -o bar.png# bar.ctl800 //x轴维度800 //y轴维度Ghir_A01,Ghir_A02,Ghir_A03,Ghir_A04,Ghir_A05,Ghir_A06,Ghir_A07,Ghir_A08,Ghir_A09,Ghir_A10,Ghir_A11,Ghir_A12,Ghir_A13 //参考染色体Chromosome_1,Chromosome_2,Chromosome_3,Chromosome_4,Chromosome_5,Chromosome_6,Chromosome_7,Chromosome_8,Chromosome_9 //目标染色体 123# 还有两个作图的工具也不一一试了,以后有需要再做# family_circle_plotter 基因家族同源基因圆形图,红线连接一个基因家族所有同源基因# family_tree_plotter 基因家族树,共线性基因对用红线连接,串联重复基因用蓝线连接 可以看到这个软件绘图还是非常简单方便的,不过图片质量不是很好,作图的像素点大小都是由自己定义的,因此排版缩放也很容易失真。还是比较推荐JCVI一类的软件,引入多种过滤参数功能更强大,且做的图是矢量图,比较好看……而且有docker镜像,不怕安装依赖的问题: 1singularity -d build jcvi.sif docker://tanghaibao/jcvi 以后有空再更新吧~ 2023.5.8 更新上面例子仅仅只是作图,没有阐明具体的生物学问题。这里通过以上的数据补充几个思考: 棉花和可可的WGD(全基因组加倍)事件分别在多少年前? 在多少年前棉花和可可发生了物种分化? 在解决上面两个问题前,需要知道一个基本概念——中性进化论。 在中性进化理论中,分子水平的变异是中性的,不受自然选择的影响。也就是说对于一个基因序列而言,每个位点上的演化(也就是发生突变)速率都是恒定的,如果一个基因位点发生同义突变,氨基酸序列并未发生变化,则这种突变不会影响物种的适应性。 同义突变率ds(Ks)指平均每个同义位点上发生同义置换的数目,在物种进化中代表了进化过程的背景碱基替换率,两个物种或者同个物种之间的Ks值可以通过上面的MCScanX的下游分析程序add_ka_and_ks_to_collinearity.pl计算得出。 如果一个物种发生了全基因组加倍事件,则会产生大量的旁系同源基因,反映在Ks值上就是有大量的Ks值接近的同源基因对产生,在Ks统计图中会出现一个峰(peak);如果两个物种之间发生了物种分化,同样产生大量的直系同源基因(物种形成的伴随事件),同样是Ks值接近的同源基因产生,并且在Ks统计图出现峰值。因此,我们可以根据同一个物种内以及不同物种间的同源基因Ks值来反推物种的WGD事件和物种分歧事件,Ks峰值处发生的事件即为最近一次的物种WGD事件或物种分歧事件。 这些事件发生的时间点,如果有已知的化石证据则最准确(根据放射性同位素衰变),如果没有就需要根据分子钟理论计算对应的时间,我们用最基础的公式T=Ks/2r。Ks,即同义突变率,平均每个位点的突变次数;r是核酸突变速度,也就是这个分类的物种每个位点每年的突变概率。 解释一下这个公式怎么来的,在两个物种分化一定的时间T后,两者都以相似的速度r累积突变(分化后是近缘物种,核酸突变速度类似),则两个物种之间核酸替换率K=T*(r+r)。实际上为了避免进化选择对突变速率的影响,这里一般用同义突变率替代核酸替换率。r值是怎么计算的呢?同样要依赖化石证据,根据两个物种共同祖先的化石时间反推r值,假设同一类物种核酸突变率类似,则这个r值还可以进一步用于别的近缘物种。 以上理论依据建立在同一类物种核酸突变率类似的假设中,实际不一定如我们所愿,且化石依据也会存在一定误差。这个r值也只是估计个大概,实际上只要在10的-9次方数量级,算出来的时间能自圆其说就行。 12345# 利用MCScanX计算物种内部的共线性结果./MCScanX -b 1 Data/ExerciseData/Gh_Tcacao_1# 利用MCScanX计算两个物种之间的共线性结果./MCScanX -b 2 Data/ExerciseData/Gh_Tcacao_2 分别整理棉花与棉花、可可与可可、棉花与可可的基因组共线性结果,只选取最后一列ks值,用R做Ks密度分布图,可以参考现有的教程: Ks密度曲线分布图绘图 - 简书 (jianshu.com) Finding Peak Values For a Density Distribution (ianmadd.github.io) 找到Ks密度分布图的峰值,选一个参考文献里合适的r值,就可以计算上面两个问题了。 顺便补充一下,下面这篇教程里有一个python脚本可以提取最长转录本,因为原作者给的是图片懒得敲下来试了,看了下代码逻辑是处理pep文件,根据序列名比较同一个基因的不同转录本长度,选取最长的那个。逻辑没有问题,因为不是刚需,有需要自己再去复现,就不重复造轮子了。 WGD(全基因组复制)分析——Ka/Ks及4Dtv值计算 - 简书 (jianshu.com)","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"比较基因组学","slug":"比较基因组学","permalink":"http://www.shelven.com/categories/%E6%AF%94%E8%BE%83%E5%9F%BA%E5%9B%A0%E7%BB%84%E5%AD%A6/"}],"tags":[{"name":"MCScanX","slug":"MCScanX","permalink":"http://www.shelven.com/tags/MCScanX/"},{"name":"共线性分析","slug":"共线性分析","permalink":"http://www.shelven.com/tags/%E5%85%B1%E7%BA%BF%E6%80%A7%E5%88%86%E6%9E%90/"}]},{"title":"基因组注释(5)——预测基因筛选","slug":"基因组注释(5)——预测基因筛选","date":"2023-04-11T14:58:20.000Z","updated":"2023-04-17T11:41:58.000Z","comments":true,"path":"2023/04/11/a.html","link":"","permalink":"http://www.shelven.com/2023/04/11/a.html","excerpt":"注释得到的基因集中,可能某些基因存在被转座子插入的情况,该基因会在后续功能注释的时候被注释上,但实际在基因组中该基因可能已经被插入失活。因此在基因组的功能注释前,需要用检测转座子软件(如TransposonPSI、TEsorter等)将含有转座子的基因找出并去除。","text":"注释得到的基因集中,可能某些基因存在被转座子插入的情况,该基因会在后续功能注释的时候被注释上,但实际在基因组中该基因可能已经被插入失活。因此在基因组的功能注释前,需要用检测转座子软件(如TransposonPSI、TEsorter等)将含有转座子的基因找出并去除。 这里记录下TEsorter软件筛选预测基因的方法。 1. TEsorter安装TEsorter原本是用于调用LTR_retriever鉴别长末端重复序列反转座子(LTR-RTs),也可以用于其他类型TE的鉴别,其鉴定原理为将待测序列与数据库REXdb(整合viridiplantae_v3.0 + metazoa_v3)的TE序列进行比对。 也可以使用GyDB数据库进行比对,官网上有具体的参数用法。 zhangrengang/TEsorter: TEsorter: an accurate and fast method to classify LTR-retrotransposons in plant genomes (github.com) TEsorter提供conda安装,但是我没有安装成功,这里还是新建conda环境后手动安装各种依赖: 1234567891011121314conda create -n "TEs"conda activate TEsconda install python==3.11 # 官方要求python版本高于3,否则运行会报错conda install biopythonconda install xopenconda install hmmerconda install blastgit clone https://ghproxy.com/https://github.com/zhangrengang/TEsorter.git # 从github镜像网站下载cd TEsorterpython setup.py installTEsorter TEsorter/test/rice6.9.5.liban # 测试 2. TEsorter运行这里有一个问题,Braker预测基因有gtf和gff3两种格式的输出结果,但是两者的行数不一样。braker.aa蛋白序列和braker.codingseq基因序列的条数与gtf文件中的transcript条数一致,但是比gff3文件中的mRNA条数多(按理来说两者应该是一致的)。 后来发现Braker加入--gff3参数生成的gff3文件mRNA数量 + gtf文件的mRNA数量 = gtf文件的transcript数量,不理解为什么有这种关系,方便起见我这里处理了gtf结果文件。 TEsorter软件可以输入基因序列或者蛋白序列,以基因序列为例,简单编写脚本如下: 12345#!/bin/bash#SBATCH -n 8#SBATCH -t 7200TEsorter /public/home/wlxie/baima_pre_mydb/braker.codingseq -eval 1e-6 -p 8 大约十几分钟运行完毕。 3. 结果文件处理结果文件如下: .tsv后缀的文件中以列表形式列出了所有预测的TE类型,一个基因可能有多种类型的TE插入,因此需要处理结果文件,统计含有TE的基因,并在gtf结果文件中将该基因去除。 12345678# 筛选含有TE的基因grep -v "^#" braker.codingseq.rexdb.cls.tsv | cut -f1 | sort | uniq | cut -f1 -d "_" | sort | uniq > TE-genes.txt# 去除含有TE的基因序列grep -Fvf /public/home/wlxie/biosoft/TEsorter/baima_mydb/TE-genes.txt braker.gtf | awk '$3 ~ /gene/' > baima_gene_only.gtf# 去除含有TE的转录本序列grep -Fvf /public/home/wlxie/biosoft/TEsorter/baima_mydb/TE-genes.txt braker.gtf | awk '$3 ~ /transcript/' > baima_transcript.gtf 可以看看去除TE序列后的转录本和基因数量: 123456(base) wlxie 17:03:52 ~/baima_pre_mydb$ cat baima_transcript.gtf | wc -l23716(base) wlxie 17:04:05 ~/baima_pre_mydb$ cat baima_gene_only.gtf | wc -l20742 本想通过gffread软件根据处理后的gtf文件重新提取基因组的蛋白序列,但是运行过程中总是报错no genomic sequence available,原因暂时未知(可能是因为braker预测结果braker.gtf不是标准的gtf文件格式,同样用gffread做gtf2gff转换的时候会有部分信息丢失)。 可以直接写一个脚本处理braker.aa文件,根据前面筛选的TE-genes.txt文件,去除含有TE的蛋白序列,这里后续用到再做更新。 2023.4.13 更新gffread报错的原因找到了: 基因组文件的序列编号有空格 gtf文件缺少必要的位置信息 主要还是跑braker过程的疏忽和对gtf以及gff3数据格式的不了解。 在braker.log日志文件中有提示,基因组的fasta文件的header中包含了空格,可能会导致后续的错误,因此braker运行时自动将空格替换为了下划线“_”,这就导致了预测后的gtf文件与基因组文件无法匹配上,自然就报错no genomic sequence available。 解决方法: 手动将基因组文件中的header部分的空格用下划线“_”代替(我的基因组是59条contig,也就是手动改59个空格),并删除原fai索引文件(一定要删除,否则仍然无法找到基因序列)。 用gffread软件做gtf和gff3格式相互转换的时候,确实会损失一部分信息,但仍然会保留最基本的CDS信息。如果直接从gtf文件的第三列提取gene和transcript信息保存成新的gtf文件,这个新的gtf文件是无法用gffread定位和提取蛋白序列的。 这一点在GTF官方文档对第三列的<feature>解释中有提到: <feature>The following feature types are required: “CDS”, “start_codon”, “stop_codon”. The features “5UTR”, “3UTR”, “inter”, “inter_CNS”, “intron_CNS” and “exon” are optional. All other features will be ignored. The types must have the correct capitalization shown here. 也就是说我如果从第三列只提取gene或者transcript,这些feature是会被忽略的,使用gffread提取蛋白序列会提示这是一个非法的GTF文件。 因此,正确的筛选方式和提取蛋白的方式应该为: 12345678# 筛选含有TE的基因grep -v "^#" braker.codingseq.rexdb.cls.tsv | cut -f1 | sort | uniq | cut -f1 -d "_" | sort | uniq > TE-genes.txt# 去除含有TE的序列grep -Fvf /public/home/wlxie/biosoft/TEsorter/baima_mydb/TE-genes.txt braker.gtf > baima_rmTE.gtf# 从基因组重新提取去除了TE的蛋白序列gffread baima_rmTE.gtf -g /public/home/wlxie/biosoft/db_data/baima/RepeatMasker_soft/genome.nextpolish.fasta.masked -y pep_rmTE.fa","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"TEsorter","slug":"TEsorter","permalink":"http://www.shelven.com/tags/TEsorter/"}]},{"title":"基因组注释(4)——基因预测","slug":"基因组注释(4)——基因预测","date":"2023-04-03T14:09:26.000Z","updated":"2023-04-08T02:38:55.000Z","comments":true,"path":"2023/04/03/a.html","link":"","permalink":"http://www.shelven.com/2023/04/03/a.html","excerpt":"在对基因组重复序列和ncRNA进行注释后,接下来是基因预测和功能注释,这也是寻找功能基因的基础和前提。这里主要记录下怎么用Braker3进行基因组的基因预测(也就是结构注释)。","text":"在对基因组重复序列和ncRNA进行注释后,接下来是基因预测和功能注释,这也是寻找功能基因的基础和前提。这里主要记录下怎么用Braker3进行基因组的基因预测(也就是结构注释)。 基因预测的方法主要有三种: 基于隐马尔可夫模型的自训练和迭代,获得从头预测的基因结构模型(软件Augustus、GeneMark-ES等) 基于已发表的近缘物种基因序列、蛋白序列的同源预测(软件DIAMOND、GeMoMa等) 基于本物种的RNA-Seq转录组数据,比对基因组内含子结构模型和基因侧翼序列信息(软件Hisat2、STAR等) 一般的流程是将以上三种基因预测结果通过软件EvidenceModeler(EVM)进行整合,最终得到预测结果gff文件。网上对于上面的流程有很多教程,针对不同软件有不同的设置,比如知乎的这篇文章使用AUGSTUS + Geneid + GeneMark + GeMoMa + GenomeThreader + Exonerate 进行基因结构预测 - 知乎 (zhihu.com) 为了简化流程,现在也有越来越多的基因预测pipeline工具得以开发,比较有名的就是Braker和Maker,感兴趣的话可以做两者预测结果的比较,我这里就用发表时间比较近的Braker3为例。 Braker本质上是一个结合了多种基因组注释工具的perl程序,其核心为braker.pl文件。 1. 安装Braker3目前为止(2023年4月3日)Braker的最新版本为3.0.2,conda上能搜到的最新版本只有2.1.6,因此不建议用conda安装,尤其是最新的版本Braker可以直接使用RNA-seq和蛋白数据,整合GeneMark-ETP和AUGUSTUS训练和预测基因,对于预测结果有较高的支持度。 因为整个pipeline包含了十几个注释用的软件,用到的perl模块也非常多(数了一下配置环境需要安装20个perl模块),还是推荐用给官方给的**container**。 1.1 申请和下载GeneMark-ETP密钥在Braker3中使用RNA-seq数据和蛋白数据预测基因,都要用到GeneMark-ETP这个软件。但是这个软件不能直接用,需要到GeneMark网站申请和下载对应的密钥文件放在集群用户的家目录中。 申请完成之后获得名称为gm_key_64.gz的密钥文件,解压之后命名为.gm_key(注意点号)并上传到集群用户的家目录下即可。 12gunzip gm_key_64.gzmv gm_key_64 .gm_key 1.2 创建Braker3镜像这一步在dockerhub网站的Braker3仓库中有详细说明,我这里选择创建singularity镜像: 1singularity build braker3.sif docker://teambraker/braker3:latest 得到的braker3.sif就是Braker3的singularity image 创建braker3镜像文件的环境变量: 1export BRAKER_SIF=/your/path/to/braker3.sif 可以复制三个示例脚本到当前目录: 123singularity exec -B $PWD:$PWD braker3.sif cp /opt/BRAKER/example/singularity-tests/test1.sh .singularity exec -B $PWD:$PWD braker3.sif cp /opt/BRAKER/example/singularity-tests/test2.sh .singularity exec -B $PWD:$PWD braker3.sif cp /opt/BRAKER/example/singularity-tests/test3.sh . 在本地申请计算资源并跑一下三个示例脚本: 12345salloc -n 50 # 申请50个核跑test.sh,注意不要在登录节点直接运行计算程序bash test1.sh # tests BRAKER1bash test2.sh # tests BRAKER2bash test3.sh # tests BRAKER3exit # 退出并释放计算资源 2. 运行Braker3官方提供了4种BRAKER pipeline 模式: RNA-Seq数据跑BRAKER 蛋白数据跑BRAKER 整合RNA-Seq数据以及蛋白数据跑BRAKER 整合短读长与长读长的RNA-Seq数据以及蛋白数据跑BRAKER 4种pipeline模式在调用软件的方法上有区别,根据自己手上有的数据选择用哪种,我这里选择第三种。 上图是整合RNA-Seq数据和蛋白数据跑Braker的流程图,需要注意基因组文件genome.fa在输入前需要需要进行softmasking(重复序列屏蔽为小写字母),官方建议不要用hardmasking(重复序列屏蔽为N),hardmasking后预测的基因数量会偏少,因为重复序列中可能也有功能基因的部分信息,屏蔽为N后就无法检测到了。 对RNA-Seq数据的处理,首先是通过SRA tookit将SRA ID对应的fastq数据下载下来(如果本来就是fastq格式就不需要这一步),用Hisat2比对到softmasking后的参考基因组并生成bam文件,再用stringtie进行转录本组装。 GeneMark-ETP以组装后的转录本和同源蛋白数据库作为输入数据进行训练和预测,之后再用AUGUSTUS软件结合上一步的预测结果进行训练和预测,最后用TSEBRA对预测的基因集进行整合,得到最终的gtf结果文件。 barker.sh脚本可以如下编写: 123456789101112#!/bin/bash#SBATCH -n 48#SBATCH -t 7200wd=baima_preif [ -d $wd ]; then rm -r $wdfisingularity exec -B ${PWD}:${PWD} ${BRAKER_SIF} braker.pl --genome=/public/home/wlxie/biosoft/db_data/baima/RepeatMasker_soft/genome.nextpolish.fasta.masked --prot_seq=/public/home/wlxie/busco_soft/busco/test_data/eukaryota/busco_downloads/lineages/eudicots_odb10/refseq_db.faa --softmasking --threads 48 --workingdir=${wd} --rnaseq_sets_dirs=/public/home/wlxie/RNAseq/BYT2022020901/rnaseq/baima --rnaseq_sets_ids=4-216031965_raw 几个参数的解释: genome softmasking后的基因组文件位置 prot_seq 同源蛋白库的文件位置 –softmasking mask的方式 –threads 跑程序用的核数 –workingdir 工作目录位置 rnaseq_sets_dirs RNA-Seq数据所在目录 –rnaseq_sets_ids 双端测序数据文件前缀(比如我这里是4-216031965_raw_1.fq和4-216031965_raw_2.fq) 说明一下同源蛋白来源于前面做BUSCO评估的真双子叶植物单拷贝直系同源库,怎么来的详情可见这篇博客(同源蛋白库建议用官方推荐的OrthoDB或者找几个模式植物的蛋白数据合并,见博客最下方的更新) 前面说过塔大集群的计算节点没有安装singularity,所以在运行该容器的时候要在申请核在本地跑程序,并且用screen维持当前会话: 1234567screen -S singularity # 创建singularity会话salloc -n 48 -t 7200 # 申请计算资源bash barker.shscreen -r singularity # 进入singularity会话exit # 退出会话exit # 运行结束释放计算资源 正常跑完花费了9个小时时间(200Mbp大小的基因组),如果中途不幸出bug,braker支持有限度的断点重新运行,主要分为以下三个阶段: 只要有中间文件存在,就可以在这三个阶段继续加入其他参数,跳过已经运行的阶段继续运行,详情可以看官方文档https://github.com/Gaius-Augustus/BRAKER#starting-braker-on-the-basis-of-previously-existing-braker-runs 3. 结果文件可以在前面给定的工作目录中看到如下结果文件: braker.gtf——Braker预测的基因集,包括了各种不同的基因结构预测结果 braker.codingseq——fasta格式的编码序列基因集(基因序列) braker.aa——fasta格式的蛋白序列基因集(蛋白序列) braker.gff3——需要–gff3参数指定,这里我没有,就是基因集的gff3格式 Augustus/*——AUGUSTUS预测的基因集(包括gtf文件、基因序列和蛋白序列) GeneMark-ETP/*——GeneMark-ETP预测的基因集以及其他中间文件 hintsfile.gff——从RNA-Seq数据和蛋白库数据中提取的外部证据数据 可以通过awk命令查看gft文件的第三列,查看预测的编码蛋白基因数量和转录本数量: 12345$ awk '$3=="gene"' braker.gtf | wc -l17869$ awk '$3=="transcript"' braker.gtf | wc -l20832 后续就可以对这些预测的基因做质量评估,然后比对各数据库做功能注释。 2023.4.7 更新1. OrthoDB蛋白数据库下载官方推荐使用OrthoDB数据库作为同源蛋白来源。 123456789101112# 真菌Fungi: https://v100.orthodb.org/download/odb10_fungi_fasta.tar.gz# 后生动物Metazoa: https://v100.orthodb.org/download/odb10_metazoa_fasta.tar.gz # 节肢动物 - Arthropoda: https://v100.orthodb.org/download/odb10_arthropoda_fasta.tar.gz # 脊椎动物 - Vertebrata: https://v100.orthodb.org/download/odb10_vertebrata_fasta.tar.gz# 单细胞生物Protozoa: https://v100.orthodb.org/download/odb10_protozoa_fasta.tar.gz# 绿色植物Viridiplantae: https://v100.orthodb.org/download/odb10_plants_fasta.tar.gz 我这里要分析的物种是植物,所以下载最后一个绿色植物蛋白库: 123nohup wget https://v100.orthodb.org/download/odb10_plants_fasta.tar.gz &tar zxvf odb10_plants_fasta.tar.gzcat plants/Rawdata/* > plant_proteins.fasta 绿色植物蛋白库约780Mb大小,建议用wget下载,如果windows下载再ftp拖拽上传到集群可能会损坏文件(并且没有md5效验码没办法确认是否真的损坏)。 解压并将所有数据合并到一个文件plant_proteins.fasta中,截至目前2023年4月7日为止,这个数据库共有3510742条蛋白序列,总文件大小为1.4Gb,比原来我比对的蛋白库大了1500倍。而蛋白比对是一个很缓慢的过程,因此这一步预测基因的时间将会很长,可以根据自己要做的物种确定用哪些注释比较完善的模式生物的蛋白库。 2. 自建蛋白数据库在NCBI网站上直接找一些组装注释结果较好的模式生物和近缘物种蛋白: 12345678910111213# 拟南芥(TAIR10.1)wget https://ftp.ncbi.nlm.nih.gov/genomes/all/GCF/000/001/735/GCF_000001735.4_TAIR10.1/GCF_000001735.4_TAIR10.1_protein.faa.gz# 栽培烟草(Ntab-TN90)wget https://ftp.ncbi.nlm.nih.gov/genomes/all/GCF/000/715/135/GCF_000715135.1_Ntab-TN90/GCF_000715135.1_Ntab-TN90_protein.faa.gz# 水稻(IRGSP-1.0)wget https://ftp.ncbi.nlm.nih.gov/genomes/all/GCF/001/433/935/GCF_001433935.1_IRGSP-1.0/GCF_001433935.1_IRGSP-1.0_protein.faa.gz# 近缘物种coffea arabicawget https://ftp.ncbi.nlm.nih.gov/genomes/all/GCF/003/713/225/GCF_003713225.1_Cara_1.0/GCF_003713225.1_Cara_1.0_protein.faa.gz# 近缘物种coffea canephorawget https://ftp.ncbi.nlm.nih.gov/genomes/all/GCA/900/059/795/GCA_900059795.1_AUK_PRJEB4211_v1/GCA_900059795.1_AUK_PRJEB4211_v1_protein.faa.gzgunzip *.gzcat *.faa > mydb_proteins.fasta 顺便记录一下近缘物种的同源蛋白是如何找到的: plant Biology - Usadel lab (plabipd.de)这个网站记录了多种已发表的植物基因组文章和数据,点击cladogram view可以直观地看到已测过基因组的植物学名和树状图,比如我要找的物种是夹竹桃科(Apocynaceae),直接ctrl + F 就可以定位到夹竹桃科所处的进化节点。 然后用Apocynaceae祖先节点和子节点的已发表基因组的植物学名,一个一个去搜NCBI网站的Genome库,有protein序列的就可以直接下载。 两种同源蛋白建库方式预测的基因数量和花费的时间: OrthoDB Plant数据库 自建蛋白数据库 花费时间 13 h 12.5 h 预测基因数 23746 23953 用自建蛋白数据库跑braker预测的基因数更多,且花费时间更短。后续以该预测结果继续分析。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"Barker3","slug":"Barker3","permalink":"http://www.shelven.com/tags/Barker3/"}]},{"title":"Apptainer/Singularity使用方法记录","slug":"Apptainer-Singularity使用方法记录","date":"2023-03-28T16:05:34.000Z","updated":"2023-03-28T16:21:56.000Z","comments":true,"path":"2023/03/29/a.html","link":"","permalink":"http://www.shelven.com/2023/03/29/a.html","excerpt":"在做生信分析的时候,难免会遇到一个pipeline上的软件存在冲突的情况,一般的解决方法是创建不同的conda环境,然后分别在不同的环境下跑不同的软件。这种操作可以解决环境冲突的问题但不适合写流程化的脚本,同时又非常占用空间。有的软件整合了pipeline流程的所有软件,按照顺序进行调用,这种软件虽然可以节省时间实现自动化分析,但是环境依赖的问题更加复杂,因此这一类的软件也往往提供容器来方便人们在一个封闭的环境中使用。","text":"在做生信分析的时候,难免会遇到一个pipeline上的软件存在冲突的情况,一般的解决方法是创建不同的conda环境,然后分别在不同的环境下跑不同的软件。这种操作可以解决环境冲突的问题但不适合写流程化的脚本,同时又非常占用空间。有的软件整合了pipeline流程的所有软件,按照顺序进行调用,这种软件虽然可以节省时间实现自动化分析,但是环境依赖的问题更加复杂,因此这一类的软件也往往提供容器来方便人们在一个封闭的环境中使用。 这篇博客主要讲一讲关于容器的一些基本常识,以及记录下学校集群中singularity的使用方法。 1. 容器(Container)前面说到为了规避软件与现有环境依赖冲突,我们往往会把一个pipeline的软件封装到一个容器中。容器是一种在Linux系统上广泛采用的应用封装技术,它将可执行程序与依赖库打包成一个镜像文件,启动时与宿主节点共享操作系统内核。 镜像(Image):可执行的独立软件包,用于保存环境 实例(Instance):基于镜像启动的运行实例,运行实际任务,不同实例之间互相隔离 由于这个镜像文件自带了可执行文件和依赖库,因此不需要用到宿主机的依赖库,也就从源头上避免了环境冲突的情况。听起来这种实现方式类似于虚拟化技术,但还是有一些区别的:前面说过容器启动时与宿主机共享操作系统内核,没有运行独立的操作系统任务,在资源的占用上明显低于虚拟机。虚拟化可以认为它更全面和彻底一些,每个虚拟机从宿主机的物理框架中分割出来,有自己的一整套操作系统,会运行各种独立的操作系统任务,即使没有运行程序也会消耗内存和系统资源。 上图来自Microsoft Azure容器与虚拟机的比较。总结来说,在安全性和隔离性上虚拟机优于容器,在资源占用、可移植性和运行速度上,容器优于虚拟机。 现有的容器软件比较多,Docker(5.8k star)是代表性的软件之一,其他开源容器化工具还有Podman(17.4k star,无守护进程的容器技术,无需root权限)、LXD(3.8k star,可运行多个进程)、Apptainer(以前叫singularity,2.4k star,无需root权限)、Containerd(13.5k star)和RunC(10.1k star)等。 这些开源的容器化技术软件各有其特点,详情可以点各自的链接了解,这里就不过多介绍了。主要说下塔大集群部署的singularity(现在已改名为Apptainer,很多人不知道改名了,两个名字就放一块儿说)的使用方法。 2. Apptainer/Singularity首先还是要说明以一下什么时候选择用容器,并不是说每一个软件都要用容器封装——反而这样是对系统资源的浪费。一般是在需要批量部署环境、或者快速部署一个pipeline环境的时候选择用容器。在HPC上进行大规模计算的时候,一般考虑安全性不会用Docker(需要root权限),Apptainer/Singularity这种无需root权限的容器工具是最好的选择。 需要说明一下,两年前Singularity改名为Apptainer,并且整个项目已经转移成为了Linux Foundation的一部分。Apptainer用法和Singularity几乎一模一样,可以参考https://apptainer.org/docs/user/latest/quick_start.htmlhttps://docs.sylabs.io/guides/latest/user-guide/quick_start.html#两个官方手册。因为塔大超算预装了singularity,以下统一用Singularity命令来讲解。 Singularity的镜像文件以.sif为后缀(Singularity Image File, SIF),且该文件是只读的,这和Docker镜像文件有本质上的区别。 2.1 使用镜像库获取镜像文件Singularity image文件是基于Docker image创建的: 1234567singularity -d build braker3.sif docker://teambraker/braker3:latest# 可以获取的镜像文件库(云平台)有以下几种# Sylabs cloud library library://# Docker docker://# Shub shub://# OCI registry oras://# 创建之后的文件名为braker3.sif build/pull 这两个命令都可以拉取镜像文件并创建为singularity的sif文件 创建的image路径、名字都可以在-d参数后根据需要自己改 当一个软件提供docker镜像,我们就可以通过上面的方法下载并创建一个singularity镜像,需要注意这个镜像文件是只读的。 2.2 创建自定义镜像文件这里顺带提一下如何制作自定义的SIF镜像文件: 12345678singularity -d build --sandbox ubuntu/ docker://ubuntu# 以沙盒的形式创建一个空的操作系统,放在ubuntu这个文件夹中singularity shell --writable ubuntu# --writable或者-w以可修改模式进入沙盒。也可以不用这种方法进入,直接进入对应的文件位置修改即可singularity build name.sif ubuntu# 创建sif镜像文件 进入沙盒,就可以和正常的linux操作系统一样进行安装软件,最后build制作成名为name.sif的singularity镜像,和2.1从镜像库拉取创建的镜像后续是一样的用法。 第二步以可修改模式进入沙盒时可能会有如下提示: 12WARNING: By using --writable, Singularity can't create /public destination automatically without overlay or underlayFATAL: container creation failed: mount /var/singularity/mnt/session/public->/public error: while mounting /var/singularity/mnt/session/public: destination /public doesn't exist in container 无法自动创建public这个文件夹,并且进入/var/singularity/mnt/session/这个文件夹下你会发现是空的,此时需要手动在你创建ubuntu的文件夹中创建public文件夹 mkdir public,提示缺少其他文件也是一样的处理方法,缺啥创建啥,就可以正常进入了。 需要注意,塔大集群无法制作sif镜像文件!!!会在最后一步build的时候提示permission denied,因此,创建自定义镜像文件要在自己的计算机上(拥有root权限),制作完成之后的sif镜像文件可以上传到集群中再运行。 2.3 运行镜像文件singularity主要有两种运行方式,一种是执行镜像文件中的命令 singularity exec;一种是进入交互模式singularity shell 12345singularity exec name.sif test/test.pl # 运行name.sif镜像文件中test文件夹下的test.pl程序singularity shell name.sif# 以交互模式进入name.sif镜像文件 需要注意,运行镜像文件后,singularity会自动挂载当前目录$PWD、用户家目录$HOME和宿主机的/tmp目录,对这些目录的文件进行修改会影响到原文件。对于一般的程序来说已经足够了,如果需要访问宿主机的其他目录,需要用--bind将宿主机目录映射到容器内。 12345singularity exec --bind /pub/software:/mnt name.sif python test.py# --bind挂载宿主机的文件夹,冒号前为宿主机的路径,冒号后为容器中的路径。内容挂载到/mnt中singularity exec --bind /pub/software name.sif python test.py# 不写挂载点,则与宿主机的目录一致。内容挂载到/pub/software中 2.4 在集群中提交singularity作业一般来说登录节点预装了singularity,计算节点也会装singularity才对,但是无论我直接用singularity命令还是指定singularity命令的绝对路径,在sbatch提交作业后都是提示该命令不存在。很疑惑,又去查了一些资料,发现有的平台需要在作业里module load singularity之后才可以加载,但是塔大集群用这个指令仍然行不通,只有本地(登录节点)才可以使用该命令。 百思不得其解,询问集群管理员暂时没有答复,后续有新消息会更新。这里说一下我的解决方法: 既然可以在本地运行singularity,那就可以用salloc申请计算资源,在本地跑程序。但是我又不可能一直坐在电脑跟前,因此需要用到screen这个工具维持当前会话。 1234567891011screen -S test# 创建名为test的session对象salloc -n 50 -t 7200# 申请50核的计算资源export BRAKER_SIF=\\$PWD/braker3.sif# 给host添加环境变量(非必须,打个比方)singularity esec braker3.sif braker.pl # 运行容器中的程序 运行之后就可以双手离开键盘,关上电脑,等待第二天程序运行结束了。 再次打开session的时候只需要运行如下命令: 12345678screen -r test# 再次打开sessionexit# 退出sessionexit# 释放计算资源 注意需要两次exit之后才会回到原来的登录节点。 singularity还有很多其他参数,对我而言暂时用不到,需要用的时候再更新。 可以参考武大超算中心的文档运行 Singularity · GitBook (whu.edu.cn)和北鲲云的介绍文档https://www.cloudam.cn/helpce/docs/2030/about2/。(可恶,我们学校集群什么时候能出一个详细的文档啊。好想吐槽,slurm调度系统和各种软件都要自己学,功能还不一定全,也不知道到底预装了哪些东西,以后要是我来管理集群一定会做详细的文档介绍所有花里胡哨的功能,而不是藏着不告诉用户) 2023.3.29更新破案了,计算节点确实没装singularity(噎住.jpg)","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"容器","slug":"容器","permalink":"http://www.shelven.com/tags/%E5%AE%B9%E5%99%A8/"},{"name":"singularity","slug":"singularity","permalink":"http://www.shelven.com/tags/singularity/"}]},{"title":"基因组注释(3)——ncRNA注释","slug":"基因组注释(3)——ncRNA注释","date":"2023-03-21T16:29:28.000Z","updated":"2023-03-21T16:31:39.000Z","comments":true,"path":"2023/03/22/a.html","link":"","permalink":"http://www.shelven.com/2023/03/22/a.html","excerpt":"非编码RNA(non-coding RNA,ncRNA)指不编码蛋白质的RNA,包括rRNA、tRNA、snRNA、snoRNA 和 microRNA 等多种已知功能的 RNA,和未知功能的RNA。tRNA预测可以使用经典的tRNAscan-SE,其他类型的RNA都可以用Infernal+Rfam数据库方式预测。","text":"非编码RNA(non-coding RNA,ncRNA)指不编码蛋白质的RNA,包括rRNA、tRNA、snRNA、snoRNA 和 microRNA 等多种已知功能的 RNA,和未知功能的RNA。tRNA预测可以使用经典的tRNAscan-SE,其他类型的RNA都可以用Infernal+Rfam数据库方式预测。 1. tRNAscan-SEtRNAscan-SE的安装需要依赖Infernal软件,因此可以用conda直接安装tRNAscan-SE顺带解决依赖问题: 1conda install -c bioconda trnascan-se 写一个脚本运行tRNAscan-SE: 12345#!/bin/bash#SBATCH -n 50#SBATCH -t 7200tRNAscan-SE --thread 50 -E -I -m tRNA_luobuma.stats -o tRNA_luobuma.out -f tRNA_luobuma.ss /public/home/wlxie/NextPolish/luobuma_rundir/genome.nextpolish.fasta 参数解释: -E 搜寻真核生物tRNA -I 使用Infernal软件进行搜索 -m 保存结果统计文件 -o 输出tRNA预测结果 -f 保存tRNA的二级结构 也可以直接使用-j参数保存gff3格式,-b参数保存bed格式,详情可以见tRNAscan-SE -h 生成的二级结构结果文件如下: str一行记录的二级结构信息,每个<>是互相配对的,代表在二级结构中这两个碱基连在一起。可以通过其他软件(如VARNA)绘制成图。 输出的out文件也推荐转成gff文件方便在基因组上可视化,因为我这里只是粗略统计一下tRNA数量,所以只看stat文件就可以了: 可以看到第一轮预测出526个tRNA,通过Infernal验证的有479个。 2. Infernal + RfamInfernal(INFERence of RNA ALignment)是Sanger实验室开发的ncRNA预测软件,他们建立了1600多个RNA家族,每个家族建立了一致性二级结构和协方差模型,也就是Rfam数据库。总体的注释思路是基因组与 Rfam数据库进行比对,Rfam是一个RNA分类数据库,其比对方法是调用软件Infernal中的程序cmscan,将提交的序列在Rfam.cm数据库中进行检索,从而得到其比对的结果。 cmscan(search sequence(s) against a covariance model database, 针对协方差模型数据库的序列搜索),主要参考官方手册Userguide.pdf (eddylab.org)中的Searching the Rfam CM database with a query sequence步骤。 前面已经安装了Infernal,这里需要再下载一个Rfam数据库。 1234567# 下载Rfam数据库,注意两个文件版本必须一致wget ftp://ftp.ebi.ac.uk/pub/databases/Rfam/CURRENT/Rfam.cm.gzwget ftp://ftp.ebi.ac.uk/pub/databases/Rfam/CURRENT/Rfam.clanin# 解压建库gunzip Rfam.cm.gzcmpress Rfam.cm 官方手册这里使用默认参数运行cmscan,稍微注意下-Z,以下是官方对-Z参数的定义: -Z Calculate E-values as if the search space size was megabases (Mb). Without the use of this option, the search space size changes for each query sequence, it is defined as the length of the current query sequence times 2 (because both strands of the sequence will be searched) times the number of CMs in . Z值代表搜索数据库的大小(database size),是和E-Values计算相关的,在默认情况下,每一个query sequence的-Z参数值是不同的,等于query sequence本身的碱基数*2*CM数据库中模型的数量,只有E-Values小于10的hits会被报道。 在作者的原文中,可以找到这么一句话: To manually set the database size used in the E-value calculation to megabases when running cmsearch or cmscan on the command line, use the -Z option. It makes sense to do this if, for example, a large sequence file has been split up into many smaller files, and searches have been performed in parallel on a compute cluster, with the results combined. In that scenario, if is set as the total number of models used times the total number of nucleotides in all sequence files times two (for both strands), then the combined results should have appropriate E-values. That is, the expectation is that in the collection of all hits between all sequences and models there will be about 1 hit with an E-value of 1 or below by chance (not due to homology), about 10 with an E-value of 10 or below by chance, etc. Non-coding RNA analysis using the Rfam database - PMC (nih.gov) 也就是说一个大的序列文件被分割成数个文件,并在一个集群上并行搜索,最终将结果文件整合的时候,设置Z值为使用的CM模型数量*所有序列文件的核苷酸总数*2,合并的结果会有一个适当的E-value值。 前面建库的时候可以看到CM数据库中模型的数量: CM模型的数量不可以直接用cat Rfam.cm | grep "ACC" | wc -l这种方式查询,你会发现这种只搜索ACC或者NAME字段查找到的数量是实际数量的两倍(包括了HMM filter)。 Z值= 基因组核苷酸数*2*数据库中模型数量/1000000 1esl-seqstat my-genome.fa # HMMER插件,统计基因组大小,计算Z值用 因此这里的Z值计算如下:$$Z = 22308888634108/1000000=1896982.90$$但是很神奇的是,在Rfam的帮助文档中给了一个对古菌Methanobrevibacter ruminantium注释ncRNA的例子,其中也用了Rfam数据库的,计算Z值时没有乘以CM模型的数量,我也有点疑惑,以下是链接地址。 Genome annotation — Rfam Help documentation For the purposes of Infernal, the total database size is the number of nucleotides that will be searched, in units of megabases (Mb, millions of nucleotides). So, it is the total number of nucleotides in all sequences that make up the genome, multiplied by two (because both strands will be searched), and divided by 1,000,000 (to convert to millions of nucleotides). 就 Infernal 而言,数据库总大小是将要搜索的核苷酸数量,以兆碱基(Mb,百万核苷酸)为单位。因此,它是构成基因组的所有序列中的核苷酸总数乘以2(因为将搜索两条链),然后除以 1,000,000(转换为Mb)。 两种方法计算Z值相差4000多倍,我无法断定哪种是正确的,后续有更深的理解再更新(也许是版本问题?)。 这里我按照文章作者给的默认参数对基因组进行注释(也就是没有指定Z值,每条序列的Z值是变动的)。 运行脚本如下: 12345#!/bin/bash#SBATCH -n 50#SBATCH -t 7200cmscan --cut_ga --rfam --nohmmonly --tblout luobuma.tblout --fmt 2 -o luobuma.out --clanin Rfam.clanin Rfam.cm /public/home/wlxie/NextPolish/luobuma_rundir/genome.nextpolish.fasta 参数解释: –cut_ga 指定Rfam GA阈值,决定哪些hits可以报告。有多种标准,可以见Glossary — Rfam Help documentation –fram 以快速模式运行 –nohmmonly 决定所有模型都是CM模型(非HMM模型) –tblout 表格形式输出结果 –fmt 2 输出格式2,包括overlapping hit的注释 -o 标准输出文件 –clanin Rfam.clanin文件的位置,该文件记录哪些模型属于同一家族 最终获得luobuma.out的标准输出文件和整理成表格的luobuma.tblout文件,这里整理一下表格文件: 27列对应预测ncRNA类型和信息;前后都有#键注释的行,除此之外每一行是预测的ncRNA具体内容 需要注意olp这一列,在infernal 1.1.4版本这一列有以下四个值: * indicates this hit does not overlap with any other reported hits 这条序列与其他已报道的序列之间无重叠区域(保留) ˆ indicates that this hit does overlap with at least one other hit, but none of the hits that overlap with it have a lower E-value (occur above it in the hit list) 这条序列与至少一条已报道的序列之间有重叠区域,但是这条序列的E-value最低(保留) $ indicates that this hit does overlap with at least one other hit that does have a lower E-value (occurs above it in the hit list) but none of those higher scoring hits have ˆ in this column 这条序列与至少一条已报道序列之间有重叠区域,且其他序列E-value更低但不是最低的(过滤,1.1.2版本的infernal中没有) = indicates that this hit does overlap with at least one other hit that has a lower E-value (occurs above it in the hit list) and does itself have a ˆ in this column 这条序列与至少一条已报道序列之间有重叠区域,且其他序列的E-value值更低且是最低的(过滤) 我们只关注预测结果中准确度最高的ncRNA,记录种类和长度,因此可以写个python脚本处理并统计数据。 因为这里输出的列表没有具体给出分类,因此对数据处理前要去Rfam官网的Entry type search栏下找到每个accession号对应的ncRNA类型Rfam: Search Rfam: 我这里只关心上面红框中的ncRNA(根据情况自己选择),勾选之后点击submit: 拉到最底下看到官网提供未格式化的列表,点击show,将显示的列表复制粘贴到一个新建的accession.txt文档,接下来就是对tblout文件和accession.txt文件处理: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970'''Infernal预测ncRNA结果文件统计脚本2023.3.22'''# 结果文件数据过滤,获取每条预测ncRNA的accession号和长度loci_length = []accession = []with open('./luobuma.tblout', 'r') as input: for i in input.readlines(): if i.find('#') != -1 or i.find('=') != -1 or i.find('$') != -1: continue else: lst = i.strip().split() if len(lst) < 1: continue length = abs(int(lst[10]) - int(lst[9])) loci_length.append(length) accession.append(lst[2])len_sum = 0for i in loci_length: len_sum += i# 处理accession.txt,提取accession号和ncRNA类型的关系accession_num = []dicts = {}with open('./accession.txt', 'r') as ac: for i in ac.readlines(): m = i.strip().split('\\t') # 以制表符分割 accession_num.append(m[0]) nc_type = m[2].split(';')[1].strip() # 获取第三列第二个分号处的ncRNA类型 if m[0] not in dicts: dicts[m[0]] = nc_type# 统计结果文件accession号对应的ncRNA类型数量mi = s = sn = lnc = t = r = other = 0mi_len = s_len = sn_len = lnc_len = t_len= r_len = other_len =0for i in range(len(accession)): item = accession[i] if item in dicts: if dicts[item] == 'miRNA': mi += 1 mi_len += int(loci_length[i]) elif dicts[item] == 'sRNA': s += 1 s_len += int(loci_length[i]) elif dicts[item] == 'snRNA': sn += 1 sn_len += int(loci_length[i]) elif dicts[item] == 'lncRNA': lnc += 1 lnc_len += int(loci_length[i]) elif dicts[item] == 'tRNA': t += 1 t_len += int(loci_length[i]) else: r += 1 r_len += int(loci_length[i]) else: other += 1 other_len += int(loci_length[i])outputlst = [('miRNA', mi, mi_len), ('sRNA', s, s_len), ('snRNA', sn, sn_len), ('lncRNA', lnc, lnc_len), ('tRNA', t, t_len), ('rRNA', r, r_len), ('others', other, other_len), ('total', len(accession), len_sum)]# 输出结果with open('./res.xls', 'w') as output: output.write('Type\\tCopy Number\\tTotal length(bp)\\n') for i in outputlst: type = str(i[0]) number = str(i[1]) length = str(i[2]) output.write(type + '\\t' + number + '\\t' + length + '\\n') 我对python掌握的不好,统计的地方可以写个循环的,怕自己绕不清楚这里就用最笨的方法….. 结果统计如下: tRNA这里预测470,与上面的tRNAscan-SE预测的479个基本没有差别。 顺带说一下,Rfam官网上也说了这种方法可以统计所有类型的RNA,如果针对不同ncRNA有特殊需求的话,可以用不同软件进行分析(但是RNAMMER现在似乎已经用不了了)。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"tRNAscan-SE","slug":"tRNAscan-SE","permalink":"http://www.shelven.com/tags/tRNAscan-SE/"},{"name":"Rfam/Infernal","slug":"Rfam-Infernal","permalink":"http://www.shelven.com/tags/Rfam-Infernal/"}]},{"title":"SSL证书申请和部署","slug":"SSL证书申请和部署","date":"2023-03-17T10:00:39.000Z","updated":"2023-04-13T08:41:09.000Z","comments":true,"path":"2023/03/17/a.html","link":"","permalink":"http://www.shelven.com/2023/03/17/a.html","excerpt":"不知不觉这个小破站运行也快要一年整了,一年前网站刚开放,我天天修bug到凌晨两三点的情景还历历在目……主要还是自己对网站搭建框架不熟悉,看不懂代码整不清楚linux操作(虽然现在也没好到哪儿去)。一年过去了通过自学确实学习了很多计算机方面的知识,有空再做个总结吧~ 一年前想写如何部署ssl证书的,如今一年快到了正好要续上ssl证书,这篇博客算是补档吧~记录下自己的操作","text":"不知不觉这个小破站运行也快要一年整了,一年前网站刚开放,我天天修bug到凌晨两三点的情景还历历在目……主要还是自己对网站搭建框架不熟悉,看不懂代码整不清楚linux操作(虽然现在也没好到哪儿去)。一年过去了通过自学确实学习了很多计算机方面的知识,有空再做个总结吧~ 一年前想写如何部署ssl证书的,如今一年快到了正好要续上ssl证书,这篇博客算是补档吧~记录下自己的操作 1. SSL部署的意义前面在http原理部分说过,HTTPS的安全基础是ssl,部署ssl之后有以下优点: 建立数据信息安全通道,保障信息安全 有https协议的网站更容易被google、baidu收录 用户浏览https协议的网站地址有锁头标志,不会显示信息安全提醒页面 2. 申请SSL证书我是在腾讯云上购买的轻量云服务器,在哪个服务器供应商买的服务器就到对应的平台,搜索SSL证书,点击申请免费证书 选择第一个免费版即可,填写你要绑定的域名,验证方法选择手动DNS验证,提交申请 这一步需要到对应的域名供应商那里进行DNS解析 我是在阿里云买的域名,因此在阿里云控制台找到你要绑定的域名,添加记录,输入上面图红框里的对应信息 回到腾讯云,点击验证域名即可,后续根据自己的服务器类型(我是Apache 服务器)选择对应的证书。第一次申请的话可能会让你完善身份信息。需要注意,如果域名被托管到其他平台,需要到对应的托管平台进行DNS解析,否则会查询不到解析记录。 3. 部署SSL证书申请成功后,进入SSL证书管理平台,点击已签发,就可以看到刚刚申请的SSL证书了,首先把证书下载到本地 解压可以看到如下四个文件,.crt后缀的是证书链和证书文件,.csr后缀的是提供给CA的文件,.key后缀是私钥文件 再次强调一下Apache服务器、Nginx服务器等等的部署目录是不同的,我这里是apache服务器,需要将上面的四个文件上传到服务器/etc/httpd/ssl/目录下。 如果之前部署过ssl证书,到这一步以后直接service network restart重启http服务就行了。 如果是第一次安装,进入/etc/httpd/conf目录,修改 httpd.conf 配置文件: 1Include conf.modules.d/*.conf # 第56行确保该命令未被注释,用于加载SSL的配置目录 进入/etc/httpd/conf.modules.d目录,修改00-ssl.conf 配置文件: 1LoadModule ssl_module modules/mod_ssl.so # 第1行确保该命令未被注释,用于加载SSL模块 进入/etc/httpd/conf.d目录,修改ssl.conf配置文件: 123456DocumentRoot "/var/www/html" # 配置虚拟主机的位置,路径可以改,建议还是用默认的ServerName xxxxx.com # 填写证书网站名称SSLEngine on # 确保SSL功能打开SSLCertificateFile /etc/httpd/ssl/xxxxx.com.crt # 确定证书路径SSLCertificateKeyFile /etc/httpd/ssl/xxxxx.com.key # 确定私钥路径SSLCertificateChainFile /etc/httpd/ssl/root_bundle.crt # 确定证书链路径 以上配置完成后,重启网络服务service network restart,这个时候再访问网站就是https协议了。 参考自SSL 证书 Apache 服务器 SSL 证书安装部署(Linux)-证书安装-文档中心-腾讯云 (tencent.com) 需要注意下,证书到期之后不会自动部署…一般这种免费的SSL证书时间都是1年,在到期前一个月会有提醒,申请完成之后直接覆盖快到期的原证书即可。(省下了一笔自动部署的90块钱) 特别注意SSL证书部署后都是立即生效的,如果你发现网站仍然提示非安全连接,可以看看自己是否用了其他第三方加速。 比如你的网站用了CDN加速,需要同时在第三方加速平台上进行HTTPS配置更改,否则SSL证书无法生效! 比如我这里用了又拍云的CDN加速,且设置了HTTPS访问,不更改成新申请的证书就无法生效。每个CDN加速平台设置HTTPS方法不一样,这里就不详细说了。","categories":[{"name":"个人主页","slug":"个人主页","permalink":"http://www.shelven.com/categories/%E4%B8%AA%E4%BA%BA%E4%B8%BB%E9%A1%B5/"}],"tags":[{"name":"建站","slug":"建站","permalink":"http://www.shelven.com/tags/%E5%BB%BA%E7%AB%99/"}]},{"title":"基因组注释(2)——散在重复序列注释","slug":"基因组注释(2)——散在重复序列注释","date":"2023-03-16T15:13:10.000Z","updated":"2023-07-10T14:40:07.000Z","comments":true,"path":"2023/03/16/a.html","link":"","permalink":"http://www.shelven.com/2023/03/16/a.html","excerpt":"前面我们注释了串联重复序列(Tandem repeat,TR),接下来是对散在重复序列(也称转座子,transposable element,TE)进行注释。注释之后我们对所有重复序列在基因组上进行屏蔽,就可以进行后面的结构基因预测和注释了。","text":"前面我们注释了串联重复序列(Tandem repeat,TR),接下来是对散在重复序列(也称转座子,transposable element,TE)进行注释。注释之后我们对所有重复序列在基因组上进行屏蔽,就可以进行后面的结构基因预测和注释了。 1. 散在重复序列散在重复序列可以分为反转录转座子(class-I TEs)和DNA转座子(class-II TEs) 反转录转座子:通过RNA介导的copy and paste机制进行转座,主要由LTR(long terminal repeat)构成,而non-LTR根据长度又分为LINEs(long interspersed nuclear elements)和SINEs(short interspersed elements)。 DNA转座子:通过DNA介导的cut and paste机制进行转座。 这里我们用RepeatModeler和RepeatMasker两个软件跑一遍基因组散在重复序列注释的流程,需要注意下因为前面做了TRF注释串联重复序列,我们运行RepeatMasker的时候要改下下参数设置。 2. RepeatModeler和RepeatMasker安装不建议用conda安装两款软件的本体(但是可以安装其他依赖) RepeatMasker配置成功过是RepeatModeler配置的前提条件,且两者之间有版本关联(比如最新的RepeatModeler版本为2.0.4,需要最新的RepeatMasker版本4.1.4安装为前提),conda直接安装RepeatMasker会导致RepeatModeler无法找到RepeatMasker的路径,且输入正确路径也会提示找不到(不知道是不是我的原因)。 下载源码包编译,可以看官网RepeatMasker Home Page。 本篇博客所使用RepeatMasker版本为4.1.2,RepeatModeler版本为2.0.3 2.1 RepeatMasker安装本体安装过程不多说,主要说一下加载Repbase数据库: RepeatMasker自带的重复序列数据库是Dfam数据库,这是一个转座子(TE)序列数据库,收录的物种比较少。Repbase是重复序列参考数据库,其中收录了大部分真核物种,适用于重复序列的同源预测。然而Repbase不是RepeatMasker自带的,需要额外下载,我这里提供20181026版本的Repbase下载地址:点击这里 下载Repbase数据库后用tar -xvf解压,将RMRBSeqs.embl和README.RMRBSeqs两个数据库文件放在RepeatMasker安装目录的Libraries目录下,注意不要修改后缀名。 在RepeatMasker安装目录下运行perl ./configure,一路回车确定路径,如果有缺失的依赖就用conda下载,一直到最后选择序列搜索比对的软件,我这里输入3回车,之后的界面再输入5回车确认: 当看到提示信息Dfam和RBRM(也就是RepBase数据库)两个数据库版本的时候,就说明加载Repbase数据库成功了。 用RepeatMasker -h查看是否可以正常运行,如果提示Devel::Size这个perl模块缺失,可以用conda安装: 1conda install -c bioconda perl-devel-size 最后需要修改一下环境变量(不修改运行的时候找不到pm文件),将RepeatMasker 安装路径添加到PERL5LIB环境变量中: 12# 打开 ~/.bashrcexport PERL5LIB="/public/home/wlxie/miniconda3/envs/biosoft/share/RepeatMasker:$PERL5LIB" 2.2 RepeatModeler安装安装过程与RepeatMasker差不多,有一个比较坑的地方是官方可选的一部分软件(比如CD-HIT)在configure过程中是必须指定的,所以还是按照github上的说明将所有依赖都用conda安装好。Dfam-consortium/RepeatModeler: De-Novo Repeat Discovery Tool (github.com) 接下来在RepeatModeler安装的目录下运行perl ./configure,同样是一路回车到底确定路径,最后会询问是否需要预测LTR结构,因为我在之前的求LAI指数的博客中已经做过LTR预测,因此这一步选择n跳过,后续我会说明如何利用LTR预测数据: 3. TE注释策略因为我要注释的生物是非模式生物,在Dfam库和Repbase库中均没有该物种信息(无法在RepeatMasker软件中指定特定的物种,-species 和 -lib的参数是冲突的,需要自建数据库),因此注释所用的数据库将由以下三种数据库组成: LTR_retriever整合的LTR预测数据库(见这篇博客) 同源的(指该类群祖先和衍生节点)重复序列数据库 使用RepeatModeler从头预测序列,训练该物种的重复序列模型,构建预测的重复序列数据库 需要注意这三种数据库都需要fasta格式,将三种数据库合并之后,使用RepeatMasker -lib指定自建数据库,预测TE序列。 4. 注释流程4.1 导出同源物种重复序列库前面2.1步骤将Repbase和Dfam数据库整合之后,RepeatMasker/Libraries目录下RepeatMaskerLib.h5这个文件为整合后构建的数据库文件,我们要在这个文件中导出同源物种的重复序列。 在RepeatMasker目录下提供了famdb.py这个程序查询目标近缘物种。如果你不知道自己的物种在什么分支上,我这里推荐一个查找已发表的植物基因组的网站Published Plant Genomes (plabipd.de),可以一级一级查看哪些近缘物种有人做过了。用以下命令查看物种重复序列否收录到库中: 1python famdb.py -i Libraries/RepeatMaskerLib.h5 lineage -ad lamiids # lamiids是我能查找到的最近的分支 找到最近的分支后,导出最近分支的祖先节点和衍生节点物种的重复序列库,使用内置的perl软件转换成fasta格式: 1234python famdb.py -i Libraries/RepeatMaskerLib.h5 families -f embl -a -d lamiids > lamiids.embl # 查找近缘物种及其上祖先节点,其下所有类群repeat famlies,输出格式embl。 -a ancestor,-d descendentbuildRMLibFromEMBL.pl lamiids.embl > lamiids.fasta # 转换格式为fasta,方便后续合并 4.2 RepeatModeler从头预测新建一个目录,用于存放RepeatModeler的预测结果,写一个repeatmodeler.slurm脚本: 1234567#!/bin/bash#SBATCH -n 100#SBATCH -t 7200BuildDatabase -name luobuma -engine ncbi /public/home/wlxie/NextPolish/luobuma_rundir/genome.nextpolish.fasta # 用基因组组装结果构建数据库RepeatModeler -pa 25 -database luobuma -engine ncbi # 自训练 RepeatModeler以自身基因组数据做训练集,用三种重复序列分析软件( RECON, RepeatScout 和 LtrHarvest/Ltr_retriever)进行预测,最后给出de novo预测结果。需要i说明一下,程序结束之后会给出如下四个文件: sample-families.fa de novo预测重复序列家族文件,也就是预测的重复序列库 sample-familes.stk Seed alignments RM_123456.XXXXXXXXX 中间文件(记录每一轮训练的流程和结果,仅用于中间程序崩了以后可以识别并继续跑流程) sample-rmod.log log文件 最终得到的luobuma-families.fa文件是我们需要的,里面记录了各种de novo预测的重复序列家族。中间文件具体有什么可以参考官方的github文档,这里仅仅是起到Recover from a failure的作用,中间程序没有崩就不用管它。 注意下RepeatModeler -pa参数,1 pa可以运行4个线程,我申请了100个核,这里就是25 pa可以用完所有资源。 这一步运行时间最久,100个核对200Mbp大小的植物基因组进行de novo预测重复序列,跑了17个小时。 4.3 整合数据库将4.1、4.2步骤的结果,以及前面做的LTR预测结果进行整合(都是fasta格式): 1cat lamiids.fasta luobuma-families.fa luobuma.fasta.mod.LTRlib.fa > final_luobuma_repeat.fasta # 合并同源数据库、RepeatModeler训练结果和LTR预测结果 此时得到的final_luobuma_repeat.fasta就是后一步运行RepeatMarsker需要指定的自建数据库。 4.4 RepeatMasker搜索重复序列根据需求确定参数,写一个repeatmasker.slurm脚本: 12345#!/bin/bash#SBATCH -n 100#SBATCH -t 7200RepeatMasker -nolow -no_is -pa 25 -lib final_luobuma_repeat.fasta -engine ncbi -gff -norna -dir luobuma /public/home/wlxie/NextPolish/luobuma_rundir/luobuma.fasta RepeatMasker的参数非常多,介绍一下这里用到的: -nolow Does not mask low_complexity DNA or simple repeats 不屏蔽低复杂度DNA或简单重复序列(有的学者认为simple repeat不算严格意义上的重复序列类型) -norna Does not mask small RNA (pseudo) genes 不屏蔽sRNA -no_is Skips bacterial insertion element check 跳过细菌插入元件检查 -pa 和RepeatModeler一样,1 pa是4个线程 -lib 指定自建数据库(与-species冲突) -gff 生成gff文件 -dir 指定输出目录 在输出目录下可以找到以下几种格式的文件: sample.fasta.cat.gz 基因组和数据库中参考重复序列比对详情,i代表碱基转换,v代表碱基颠换 sample.fasta.masked 重复序列屏蔽我iN后的序列 sample.fasta.out 预测的重复序列详细信息,Smith-Waterman 算法得分等等 sample.fasta.out.gff 上一个文件的gff格式 sample.fasta.tbl RepeatMasker的结果报告 主要看一下结果报告: TE的预测结果被分为逆转录转座子、DNA转座子和Unclassified三类,总的转座子序列数量和在基因组的占比见Total interspersed repeats统计结果。做到这里可以再结合前面做的TR分析,做一个基因组重复序列注释汇总表,我这里就不再演示了。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"RepeatModeler","slug":"RepeatModeler","permalink":"http://www.shelven.com/tags/RepeatModeler/"},{"name":"RepeatMasker","slug":"RepeatMasker","permalink":"http://www.shelven.com/tags/RepeatMasker/"}]},{"title":"基因组注释(1)——串联重复序列注释","slug":"基因组注释(1)——串联重复序列注释","date":"2023-03-12T15:14:40.000Z","updated":"2023-03-12T15:18:13.000Z","comments":true,"path":"2023/03/12/a.html","link":"","permalink":"http://www.shelven.com/2023/03/12/a.html","excerpt":"本系列笔记开始记录如何对组装的植物基因组进行注释。前面通过一系列组装过程,我们拿到了组装好的基因组草图,而这个基因组草图只是研究的开始,我们关注的是基因组中有哪些我们感兴趣的功能基因或者结构基因,以及怎么用这些基因阐述生物学问题等等,这个时候一个高准确度的基因组注释结果就非常重要了。","text":"本系列笔记开始记录如何对组装的植物基因组进行注释。前面通过一系列组装过程,我们拿到了组装好的基因组草图,而这个基因组草图只是研究的开始,我们关注的是基因组中有哪些我们感兴趣的功能基因或者结构基因,以及怎么用这些基因阐述生物学问题等等,这个时候一个高准确度的基因组注释结果就非常重要了。 基因组注释可以分为两部分:基因组的结构注释(重复序列识别、非编码基因预测、编码基因预测)和基因功能注释,结构注释是功能注释的基础。 这里先从结构注释中的重复序列注释开始。我们知道植物基因组多倍化频繁,且基因组中存在大量的重复序列(有的植物基因组中重复序列甚至能达到80%),这些重复序列控制植物表型调控中有非常重要的作用。基因组中的重复序列可以分为以下几种: 1. 串联重复序列注释串联重复(Tandem Repeat, TR)指DNA中的一个或多个核苷酸前后相连接的重复。串联重复又分为卫星DNA(Satellite DNA)、小卫星(Minisatellite)、微卫星(Microsatellite)。微卫星在植物中一般称为SSR(Simple Sequence Repeats)SSR在植物基因组常被用做遗传标记使用。下面我用两款软件跑下串联重复序列注释。 1.1 GMATA这个软件主要用来搜索重复单元较短的简单重复序列,也就是微卫星SSR序列。这软件运行速度比较快,而且可以同时设计SSR引物,还可以预测elect-PCR结果,或者将预测结果显示在基因组浏览器上,可以在github上找到项目地址:XuewenWangUGA/GMATA: software GMATA (github.com) 需要注意如果是在linux系统上跑一键流程的话,需要单独安装primer3和e-pcr(可以直接用conda安装),分别是设计SSR引物和模拟PCR的时候需要调用。如果没有这方面需要,可以在设置文件default_cfg.txt修改为不启用后面的模块。我这里统一用linux系统演示,只演示命令行操作,这个软件在windows上运行有UI界面,还是比较直观的。 12345#!/bin/bash#SBATCH -n 50#SBATCH -t 7200perl gmata.pl -c default_cfg.txt -i /public/home/wlxie/NextPolish/luobuma_rundir/genome.nextpolish.fasta 我这里直接用了一键流程,修改默认的设置文件中三个模块[set]:doprimer_smt、[set]:elctPCR和[set]:mk2gff3的ModulRun = N,虽然可以批量设计引物,但是我这里用不着….. 预测的SSR结果在原fasta文件路径下,以.ssr和.ssr.sat2为后缀: 在sat结果文件中,最终结果以4个表格的方式呈现,分别统计motif k-mer、motif和成对的motif信息以及最后每个contig的SSR统计信息。以上是其中两个表格。 1.2 TRF这个软件和上面软件类似,可以统计整个基因组上的串联重复序列,在上面一个软件输出结果上稍微有些不同。 12345#!/bin/bash#SBATCH -n 50#SBATCH -t 7200trf /public/home/wlxie/NextPolish/luobuma_rundir/genome.nextpolish.fasta 2 7 7 80 10 50 500 -f -d -m -r -h 说明一下这个软件使用过程中传参的一堆数字代表什么: 12345678910111213Match = matching weight # 匹配上的权重,缺省值2Mismatch = mismatching penalty # 未匹配上的权重,缺省值7Delta = indel penalty # 插入罚分,缺省值7PM = match probability (whole number) # 比对上的概率,可选值为80和75PI = indel probability (whole number) # 插入的概率,可选值为10和20Minscore = minimum alignment score to report # 串联重复序列的比对必须达到或超过要报告的比对分数MaxPeriod = maximum period size to report # 最大重复单元的bp数,不指定的话从1到2000其他主要可选参数(列一部分):-m 输出屏蔽重复序列后的基因组-f 记录每个重复序列侧翼的500个核苷酸,主要用于PCR引物设计-d 生成屏蔽数据文件,与汇总表有相同的信息,不包含标签,主要方便做其他处理-h 不生成html结果文件(contig数量多的话建议使用,否则有大量的文件生成) 运行结束后可以生成.mask后缀的屏蔽后的序列文件,还有一个.dat后缀的结果文件,包含了重复序列的详细信息。 主要讲解下这个dat文件,先less -S打开看看: 上面记录的参数和其他信息就不说了,主要是底下的数据,每一行是一个重复序列的信息,每行分为15列: 第1列和第2列是预测到的重复序列的起始和结束位点; 第3列是重复单元的长度; 第4列是重复单元的拷贝数,不一定是整数,因为可能存在插入缺失; 第5列是一致性序列的长度; 第6列是匹配的百分率; 第7列是插入缺失的百分率; 第8列是TRF软件给的分值,越高越可靠; 第9-12列分别为ACGT碱基的个数; 第13列表示比对的熵值; 第14列是一致性序列的具体碱基排列; 第15列是整个重复序列的具体碱基排列顺序。 对于结果文件的处理有两种方法,一种是将.dat后缀的结果文件转换成标准的.gff3文件格式,然后用bedtools提取trf特征。转化gff的的方法github上有不少开源的项目,这里推荐一个Adamtaranto/TRF2GFF: Convert Tandem Repeat Finder dat file output into gff3 format (github.com) 还有一种方法就是自己写个脚本,可以看到同一位点处可能有多条预测的串联重复序列,也就是说这些串联重复序列之间可能存在交叠,思路是将同一位点预测的重复序列只保留最短的一条(起始位点相同保留前一条,结束位点相同保留后一条),然后统计第3列重复序列k-mer数量和类型,根据第1列和第2列计算长度,统计总长度和占比即可。 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394'''自编TFR dat格式结果文件统计脚本2023.3.12'''from collections import Counter# 数据过滤loci_start = []loci_finish = []total_line = []with open('./genome.baima.fasta.2.7.7.80.10.50.500.dat', 'r') as input: for i in input.readlines()[15:]: if i.find('Sequence') != -1 or i.find('Parameters') != -1: continue else: lst = i.strip().split(' ') if len(lst) < 15: continue if len(loci_start) < 1 and len(loci_finish) < 1: # 处理列表为空的情况 loci_start.append(lst[0]) loci_finish.append(lst[1]) total_line.append(lst) else: if lst[0] != loci_start[-1] and lst[1] != loci_finish[-1]: # 开始结束位点都不同,则记录数据 loci_start.append(lst[0]) loci_finish.append(lst[1]) total_line.append(lst) elif lst[0] == loci_start[-1]: # 开始位点相同,跳过 continue elif lst[1] == loci_finish[-1]: # 结束位点相同,删除列表最后一个元素并加入新元素 del loci_start[-1] del loci_finish[-1] del total_line[-1] loci_start.append(lst[0]) loci_finish.append(lst[1]) total_line.append(lst)# 提取motif长度和重复序列长度motif_lst = []leng_lst = []for i in total_line: motif_lst.append(i[2]) leng = int(i[1]) - int(i[0]) + 1 leng_lst.append(leng)# 统计相同motif的总长度和所有重复序列总长度total_leng = {}motif_sum = 0for i in range(len(motif_lst)): item = motif_lst[i] motif_sum += leng_lst[i] if item in total_leng: total_leng[item] += leng_lst[i] else: total_leng[item] = leng_lst[i]# 统计motif-mer数量,总数,占比count_motif = Counter(motif_lst)count_lst = list(count_motif.items())count_lst.sort(key = lambda x : x[1], reverse = True)lst_ = []hit_num = 0for i in count_lst: hit_num += i[1] for i in count_lst: ls = i[0] lst1 = list(i) if ls in total_leng: lst1.append(total_leng[ls]) precentage = '%.2f%%'%(100 * i[1] / hit_num) lst1.append(precentage) lst_.append(lst1)# 统计结果过滤(取前二十)lst_filted = []hit_ = 0motif_ = 0for i in range(1, 20): lst_filted.append(lst_[i]) hit_ += lst_[i][1] motif_ += lst_[i][2]lst_filted.append(['Other', int(hit_num - hit_), int(motif_sum - motif_), '%.2f%%'%(100 - 100 * hit_ / hit_num)])lst_filted.append(['Total', int(hit_num), int(motif_sum), '100%'])# 输出结果with open('./stastics.xls', 'w') as output: output.write('Motif(-mer)\\tNumber\\tLength(bp)\\tPrecentage\\n') for i in lst_filted: motif = i[0] number = i[1] length = i[2] pre = i[3] output.write(motif + '\\t' + str(number) + '\\t' + str(length) + '\\t' + str(pre) + '\\n') **写的有点冗长,能实现统计功能就行…..**实现的结果如下: 可以看到两个软件统计结果还是有比较大的出入的,可能是在算法上有不同。在单独分析SSR序列的时候还是用GMATA准确一些,如果是统计全基因组上的串联重复序列则使用老牌的TRF更为合适。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"GMATA","slug":"GMATA","permalink":"http://www.shelven.com/tags/GMATA/"},{"name":"TRF","slug":"TRF","permalink":"http://www.shelven.com/tags/TRF/"}]},{"title":"绕过双重封锁部署ChatGPT到zhenxun_bot","slug":"绕过双重封锁部署ChatGPT到zhenxun-bot","date":"2023-03-08T13:54:56.000Z","updated":"2023-03-08T14:01:24.000Z","comments":true,"path":"2023/03/08/a.html","link":"","permalink":"http://www.shelven.com/2023/03/08/a.html","excerpt":"作为一个从ChatGPT公测用到现在的用户,有些无奈很难言说。本来OpenAI就不对咱们这个区域开放,使用官方的API搭建应用可以不借助VPN访问,算是解除了区域限制。但是,从2023年3月2日傍晚开始,API接口就开始没有响应了,官网没有问题,四处查询发现可能是API的域名上了GFW名单(暂不确定,有可能重大会议过去后会恢复?)。","text":"作为一个从ChatGPT公测用到现在的用户,有些无奈很难言说。本来OpenAI就不对咱们这个区域开放,使用官方的API搭建应用可以不借助VPN访问,算是解除了区域限制。但是,从2023年3月2日傍晚开始,API接口就开始没有响应了,官网没有问题,四处查询发现可能是API的域名上了GFW名单(暂不确定,有可能重大会议过去后会恢复?)。 因此,现在摆在眼前的问题是如何绕过双重封锁调用OpenAI的API接口?最稳妥的方式当然是给服务器挂个全局代理,但是我的服务器本身就在作代理服务器,给服务器再上个代理会比较麻烦……这里记录下自己实现的方式,顺便记录下是如何部署ChatGPT到zhenxun_bot(这个bot真的超级好用!)上的。 本人在这方面是小白,只是记录实现过程。 此部分内容需要以部署zhenxun_bot为前提、有一个未上GFW名单的域名(国内需要实名)。 1. 部署ChatGPT到zhenxun_bot时间过去太久,已经找不到写插件的原作者了…我是在原插件的基础上copy和修改了一部分代码,实际上就是在zhenxun_bot的AI插件基础上做的一点删改(可能有些没删干净,懒得查了)。如果没有修改路径的话,原文件路径是/zhenxun_bot/plugins/ai/data_source.py,下面代码替代原文件内容(原文件最好另存以防万一): 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238import osimport randomimport refrom utils.http_utils import AsyncHttpxfrom configs.path_config import IMAGE_PATH, DATA_PATHfrom services.log import loggerfrom utils.message_builder import image, facefrom configs.config import Config, NICKNAMEfrom .utils import ai_message_managerfrom copy import deepcopyfrom transformers import GPT2TokenizerFastimport openaiopenai.api_key = "xxxxxxxxxxxxxx"try: import ujson as jsonexcept ModuleNotFoundError: import jsonsession_config = { 'preset': '你是一个大型语言模型,可以回答我的问题。如果我有任何问题,请随时告诉你,你会尽力为我解答。', 'context': ''}sessions = {}tokenizer = GPT2TokenizerFast.from_pretrained("gpt2-large")check_url = "https://v2.alapi.cn/api/censor/text"index = 0anime_data = json.load(open(DATA_PATH / "anime.json", "r", encoding="utf8"))# 获取对话sessiondef get_chat_session(sessionid): if sessionid not in sessions: config = deepcopy(session_config) config['id'] = sessionid sessions[sessionid] = config return sessions[sessionid] def chat_with_gpt(prompt): try: resp = openai.Completion.create( model = "text-davinci-003", temperature = 0.9, max_tokens=3000, top_p=1, presence_penalty=0, frequency_penalty=0, prompt=prompt) resp = resp['choices'][0]['text'] except openai.OpenAIError as e: print('openai 接口报错: ' + str(e)) resp = str(e) return respasync def get_chat_result(text: str, img_url: str, user_id: int, nickname: str) -> str: """ 获取 AI 返回值,顺序: 特殊回复 -> GPT3 -> 青云客 :param text: 问题 :param img_url: 图片链接 :param user_id: 用户id :param nickname: 用户昵称 :return: 回答 """ global index ai_message_manager.add_message(user_id, text) special_rst = await ai_message_manager.get_result(user_id, nickname) if special_rst: ai_message_manager.add_result(user_id, special_rst) return special_rst if index == 5: index = 0 if len(text) < 6 and random.random() < 0.6: keys = anime_data.keys() for key in keys: if text.find(key) != -1: return random.choice(anime_data[key]).replace("你", nickname) rst = await GPT_3(text, user_id) if not rst: rst = await xie_ai(text) if not rst: return no_result() if nickname: if len(nickname) < 5: if random.random() < 0.5: nickname = "~".join(nickname) + "~" if random.random() < 0.2: if nickname.find("大人") == -1: nickname += "大~人~" rst = str(rst).replace("小主人", nickname).replace("小朋友", nickname) ai_message_manager.add_result(user_id, rst) return rst# GPT3接口async def GPT_3(msg: str, sessionid: int) -> str: """ 获取GPT3接口的回复 指令如下(群内需@机器人):1.[重置会话] 请发送 重置会话2.[设置人格] 请发送 设置人格+人格描述3.[重置人格] 请发送 重置人格。 注意:重置会话不会清空人格,重置人格会重置会话!设置人格后人格将一直存在,除非重置人格或重启逻辑端! """ try: if msg.strip() == '': return '您好,我是人工智能助手,如果您有任何问题,请随时告诉我,我将尽力回答。\\n如果您需要重置我们的会话,请回复`重置会话`' # 获得对话session session = get_chat_session(sessionid) if '重置会话' == msg.strip(): session['context'] = '' return "会话已重置" if '重置人格' == msg.strip(): session['context'] = '' session['preset'] = session_config['preset'] return '人格已重置' if msg.strip().startswith('设置人格'): session['preset'] = msg.strip().replace('设置人格', '') session['context'] = '' # 处理上下文逻辑 token_limit = 4096 - 3000 - len(tokenizer.encode(session['preset'])) - 3 session['context'] = session['context'] + "\\n\\nQ:" + msg + "\\nA:" ids = tokenizer.encode(session['context']) tokens = tokenizer.decode(ids[-token_limit:]) # 计算可发送的字符数量 char_limit = len(''.join(tokens)) session['context'] = session['context'][-char_limit:] # 从最早的提问开始截取 pos = session['context'].find('Q:') session['context'] = session['context'][pos:] # 设置预设 msg = session['preset'] + '\\n\\n' + session['context'] print(msg) # 与ChatGPT交互获得对话内容 message = chat_with_gpt(msg) print("会话ID: " + str(sessionid)) print("ChatGPT返回内容: ") print(message) return message except Exception as error: traceback.print_exc() return str('异常: ' + str(error)) # 屑 AIasync def xie_ai(text: str) -> str: """ 获取青云客回复 :param text: 问题 :return: 青云可回复 """ res = await AsyncHttpx.get(f"http://api.qingyunke.com/api.php?key=free&appid=0&msg={text}") content = "" try: data = json.loads(res.text) if data["result"] == 0: content = data["content"] if "菲菲" in content: content = content.replace("菲菲", NICKNAME) if "艳儿" in content: content = content.replace("艳儿", NICKNAME) if "公众号" in content: content = "" if "{br}" in content: content = content.replace("{br}", "\\n") if "提示" in content: content = content[: content.find("提示")] if "淘宝" in content or "taobao.com" in content: return "" while True: r = re.search("{face:(.*)}", content) if r: id_ = r.group(1) content = content.replace( "{" + f"face:{id_}" + "}", str(face(int(id_))) ) else: break return ( content if not content and not Config.get_config("ai", "ALAPI_AI_CHECK") else await check_text(content) ) except Exception as e: logger.error(f"Ai xie_ai 发生错误 {type(e)}:{e}") return ""def hello() -> str: """ 一些打招呼的内容 """ result = random.choice( ( "哦豁?!", "你好!Ov<", f"库库库,呼唤{NICKNAME}做什么呢", "我在呢!", "呼呼,叫俺干嘛", ) ) img = random.choice(os.listdir(IMAGE_PATH / "zai")) if img[-4:] == ".gif": result += image(img, "zai") else: result += image(img, "zai") return result# 没有回答时回复内容def no_result() -> str: """ 没有回答时的回复 """ return ( random.choice( [ "你在说啥子?", f"纯洁的{NICKNAME}没听懂", "下次再告诉你(下次一定)", "你觉得我听懂了吗?嗯?", "我!不!知!道!", ] ) + image(random.choice(os.listdir(IMAGE_PATH / "noresult")), "noresult") )async def check_text(text: str) -> str: """ ALAPI文本检测,主要针对青云客API,检测为恶俗文本改为无回复的回答 :param text: 回复 """ if not Config.get_config("alapi", "ALAPI_TOKEN"): return text params = {"token": Config.get_config("alapi", "ALAPI_TOKEN"), "text": text} try: data = (await AsyncHttpx.get(check_url, timeout=2, params=params)).json() if data["code"] == 200: if data["data"]["conclusion_type"] == 2: return "" except Exception as e: logger.error(f"检测违规文本错误...{type(e)}:{e}") return text openai.api_key需要上官网获取后填入 实际上就是把原来的图灵接口替换成GPT3接口。引入openai库和transformers库,使用了前者的openai.Completion.create()方法和后者GPT2TokenizerFast.from_pretrained()预训练的GPT2模型和分词器。 关键在于前者,因为OpenAI的API网站已经上了GFW名单,所以我们现在就算有api_key也无法调用API接口(会显示超时)。以下是解决方法。 2. 托管域名到CLOUDFLARE后面我们要用到CLOUDFLARE,没有账户的话注册一个:https://dash.cloudflare.com/ 注册之后点击右边的Websites,按照操作流程添加主域名,修改两个DNS服务器名字。 比如我这里用阿里云买了一个域名,需要登录阿里云的域名控制台,点击管理,进入右边DNS修改页面 修改原来默认的DNS服务器为lorna.ns.cloudflare.com和ram.ns.cloudflare.com。 中间可能还需要你邮件确认,按照提示操作就可以。 3. 创建CLOUDFLARE Workers该步骤参考来自github [noobnooc],感谢大佬提供的解决方案! 回到CLOUDFLARE,点击右边的创建Workers——Create a Service,这里直接确认创建一个服务。 创建之后点击Quick edit修改workers代码如下(起到代理api.openai.com的作用): 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156// Website you intended to retrieve for users.const upstream = 'api.openai.com'// Custom pathname for the upstream website.const upstream_path = '/'// Website you intended to retrieve for users using mobile devices.const upstream_mobile = upstream// Countries and regions where you wish to suspend your service.const blocked_region = []// IP addresses which you wish to block from using your service.const blocked_ip_address = ['0.0.0.0', '127.0.0.1']// Whether to use HTTPS protocol for upstream address.const https = true// Whether to disable cache.const disable_cache = false// Replace texts.const replace_dict = { '$upstream': '$custom_domain',}addEventListener('fetch', event => { event.respondWith(fetchAndApply(event.request));})async function fetchAndApply(request) { const region = request.headers.get('cf-ipcountry').toUpperCase(); const ip_address = request.headers.get('cf-connecting-ip'); const user_agent = request.headers.get('user-agent'); let response = null; let url = new URL(request.url); let url_hostname = url.hostname; if (https == true) { url.protocol = 'https:'; } else { url.protocol = 'http:'; } if (await device_status(user_agent)) { var upstream_domain = upstream; } else { var upstream_domain = upstream_mobile; } url.host = upstream_domain; if (url.pathname == '/') { url.pathname = upstream_path; } else { url.pathname = upstream_path + url.pathname; } if (blocked_region.includes(region)) { response = new Response('Access denied: WorkersProxy is not available in your region yet.', { status: 403 }); } else if (blocked_ip_address.includes(ip_address)) { response = new Response('Access denied: Your IP address is blocked by WorkersProxy.', { status: 403 }); } else { let method = request.method; let request_headers = request.headers; let new_request_headers = new Headers(request_headers); new_request_headers.set('Host', upstream_domain); new_request_headers.set('Referer', url.protocol + '//' + url_hostname); let original_response = await fetch(url.href, { method: method, headers: new_request_headers, body: request.body }) connection_upgrade = new_request_headers.get("Upgrade"); if (connection_upgrade && connection_upgrade.toLowerCase() == "websocket") { return original_response; } let original_response_clone = original_response.clone(); let original_text = null; let response_headers = original_response.headers; let new_response_headers = new Headers(response_headers); let status = original_response.status; if (disable_cache) { new_response_headers.set('Cache-Control', 'no-store'); } new_response_headers.set('access-control-allow-origin', '*'); new_response_headers.set('access-control-allow-credentials', true); new_response_headers.delete('content-security-policy'); new_response_headers.delete('content-security-policy-report-only'); new_response_headers.delete('clear-site-data'); if (new_response_headers.get("x-pjax-url")) { new_response_headers.set("x-pjax-url", response_headers.get("x-pjax-url").replace("//" + upstream_domain, "//" + url_hostname)); } const content_type = new_response_headers.get('content-type'); if (content_type != null && content_type.includes('text/html') && content_type.includes('UTF-8')) { original_text = await replace_response_text(original_response_clone, upstream_domain, url_hostname); } else { original_text = original_response_clone.body } response = new Response(original_text, { status, headers: new_response_headers }) } return response;}async function replace_response_text(response, upstream_domain, host_name) { let text = await response.text() var i, j; for (i in replace_dict) { j = replace_dict[i] if (i == '$upstream') { i = upstream_domain } else if (i == '$custom_domain') { i = host_name } if (j == '$upstream') { j = upstream_domain } else if (j == '$custom_domain') { j = host_name } let re = new RegExp(i, 'g') text = text.replace(re, j); } return text;}async function device_status(user_agent_info) { var agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"]; var flag = true; for (var v = 0; v < agents.length; v++) { if (user_agent_info.indexOf(agents[v]) > 0) { flag = false; break; } } return flag;} 修改之后点击右下角Save and deploy,此时worker地址还不能直接代替openai的API地址,需要进一步绑定前面的域名(CLOUDFLARE Workers只能绑定托管到CLOUDFLARE的域名,所以有了前面一步)。 4. 绑定域名点击Workers进入管理页面,点击Triggers——Add Custom Domain,将前面托管的域名填进去,可以用自己喜欢的二级域名: 大约过几分钟,custom domains显示Certificate 为 Activate即可。 这个时候就可以通过你绑定的域名来访问api.openai.com了,可以通过其他POST工具调试接口,就不多说了。 5. 修改openai库前面做的一系列步骤是让你可以通过其他域名访问openai的API网站,但是前面第一步写的插件调用了openai.Completion.create()方法函数,此时仍然会直接访问api.openai.com,这个时候就是扒源代码修改了。 locate openai先找到服务器上openai下载的位置,在对应的路径修改,比如我的文件路径是/root/anaconda3/lib/python3.9/site-packages/openai,修改该路径下的__init__.py文件: 第34行api_base后面的网址改为刚刚绑定的网址(/v1的部分不要动)。 上述步骤完成后,重启zhenxun_bot就可以在不对自己服务器做任何代理的情况下正常调用OpenAI的API接口了。 顺便说一下,2022年12月申请的openAI账号每个账户有18美元的额度,现在(2023年3月)申请的账号就只有5美元额度了,emmmmmmm… 不过上面的那个插件用的是text-davinci-003模型,和ChatGPT用的模型稍有不同,就在前几天ChatGPT公开了API,所使用的模型为gpt-3.5-turbo。并且我前面用的方法是Create completion,和ChatGPT创建实例的方法Create chat completion是不同的,且收费也不一样,现在ChatGPT API收费标准是0.002美元/1000 tokens,token数和字数是不一样的,要看分词器怎么分,不过现在API输出上限均为4096 token。有空再更新一下模型方法,这里只是做个记录。 详细的API调用方法需要参考官网API Reference - OpenAI API","categories":[{"name":"网络相关","slug":"网络相关","permalink":"http://www.shelven.com/categories/%E7%BD%91%E7%BB%9C%E7%9B%B8%E5%85%B3/"},{"name":"QQ机器人","slug":"QQ机器人","permalink":"http://www.shelven.com/categories/QQ%E6%9C%BA%E5%99%A8%E4%BA%BA/"}],"tags":[{"name":"ChatGPT","slug":"ChatGPT","permalink":"http://www.shelven.com/tags/ChatGPT/"}]},{"title":"0基础学习三代基因组测序组装(9)——GATK检测植物基因组SNP和INDEL变异","slug":"0基础学习基因组三代测序组装(9)——GATK检测植物基因组SNP和INDEL变异","date":"2023-03-07T09:53:32.000Z","updated":"2023-03-07T09:56:00.000Z","comments":true,"path":"2023/03/07/a.html","link":"","permalink":"http://www.shelven.com/2023/03/07/a.html","excerpt":"基因组组装完成之后,我们就可以对基因组进行变异分析了。这里主要介绍由 Broad Institute开发的一款基因组分析工具GATK,这款工具设计之初是用于处理分析Illumina二代测序技术产生的人类全外显子和全基因组数据,经过多个版本的优化迭代,GATK集合了多种高通量测序数据处理和质控的软件,如今GATK可以说是对DNA和RNA-seq数据检测SNP和Indel的标准。","text":"基因组组装完成之后,我们就可以对基因组进行变异分析了。这里主要介绍由 Broad Institute开发的一款基因组分析工具GATK,这款工具设计之初是用于处理分析Illumina二代测序技术产生的人类全外显子和全基因组数据,经过多个版本的优化迭代,GATK集合了多种高通量测序数据处理和质控的软件,如今GATK可以说是对DNA和RNA-seq数据检测SNP和Indel的标准。 1. GATK安装GATK的运行依赖于JAVA环境,目前(2023年3月6日)GATK更新到版本4.3.0,可以直接用conda下载。为了避免环境冲突,最好创建一个新环境专门用于GATK和相关变异检测工具运行。 12345conda create -n GATKconda install -c bioconda gatkconda install bwa-mem2conda install samtools bwa-mem2和samtools用于双端序列比对回基因组,需要单独下载这两个软件,后面再说。 安装完成之后可以通过gatk --help查看是否正常。 2. 流程详解流程内容主要参考官网Best Practices Workflows的文章。 GATK工具的变异注释主要包括3个部分:数据预处理(Data Pre-processing)、变异检测(Variant Discovery)和变异优化(Callset Refinement)。 以Germline short variant discovery (SNPs + Indels) ,即胚系短变异的发现为例,官网对多个样本(群组数据,Cohort Data)的变异检测分析流程如下: 单个样本(Single-Sample Data)的变异检测标准流程如下: 分析流程基本类似,以我前面组装的三代植物基因组为例跑一下这两个流程,强调一下我用的是植物基因组,后续无法用Germline的注释数据资源对变异集进行功能注释!因此这里只跑到变异集过滤的步骤,拿到SNP和INDEL。 2.1 数据预处理 构建参考基因组索引:组装的基因组作为reference参考基因组,首先需要对参考基因组建立索引,方便后续进行比对和对参考基因组进行查询。注意三个软件的索引文件不同,每个软件都必须建立索引。 1234567#!/bin/bashref=$1bwa-mem2 index $refsamtools faidx $refreferencename=`basename $ref | sed "s/fasta/dict/" ` # .fasta文件后缀改为.dictgatk CreateSequenceDictionary -R $ref -O $referencename fasta文件转化uBAM文件,标记adapter 序列:组装好的基因组格式是fasta格式,需要转换成uBAM格式(umapped的BAM文件),接着标记illumina二代测序的adapter序列。本质上都是调用了Picard工具,也可以直接用java写脚本。 123456#!/bin/bashsampleName=$1gatk FastqToSam -F1 raw_fastq/${sampleName}_1.fq.gz -F2 raw_fastq/${sampleName}_2.fq.gz -RG $sampleName -SM $sampleName -O ubam/${sampleName}.bamgatk MarkIlluminaAdapters -I ubam/${sampleName}.bam -O markadapeters/${sampleName}.markadapeters.bam -M markadapeters/${sampleName}.metrics.txt 标记后的序列转化成fastq格式,回比参考基因组,得到干净的BAM文件:第二和第三步来自官方的文章(How to) Map and clean up short read sequence data efficiently,通过联合SamToFastq、BWA - MEM和MergeBamAlignment三个程序,节省比对时间,绕过占用空间过大的SAM文件,MergeBamAlignment可以将已排序的SAM中的定义信息(我这里将SAM文件转换成BAM文件)与uBAM中的定义信息进行合并,得到干净的BAM并进行排序和构建索引。 123456789101112#!/bin/bashsampleName=$1threads=50ref=/public/home/wlxie/biosoft/GATK_file/gatk/ref/luobuma.fastagatk SamToFastq -I markadapeters/${sampleName}.markadapeters.bam -F interleaved_fq/${sampleName}_1.interleaved.fq.gz -F2 interleaved_fq/${sampleName}_2.interleaved.fq.gz -CLIP_ATTR XT -CLIP_ACT 2bwa-mem2 mem -M -t $threads $ref interleaved_fq/${sampleName}_1.interleaved.fq.gz interleaved_fq/${sampleName}_2.interleaved.fq.gz | samtools view -Sb - > raw_bam/${sampleName}.bamgatk MergeBamAlignment -R $ref -UNMAPPED ubam/${sampleName}.bam -O align_bam/${sampleName}.bam -ALIGNED raw_bam/${sampleName}.bam -MC true --CREATE_INDEX truerm -rf markadapeters/${sampleName}.markadapeters.ba interleaved_fq/${sampleName}_1.interleaved.fq.gz interleaved_fq/${sampleName}_2.interleaved.fq.gz raw_bam/${sampleName}.bam # 删除中间文件 标记重复序列:做SNP分析前最重要的一点就是标记重复序列(mark duplicate),二代测序是在PCR扩增的基础上进行的,因此PCR扩增产生的多拷贝会结合到flowcell的不同位置,生成完全相同的测序cluster,最后得到重复序列。这一步就是标记这些重复序列(但是没有删除,对结果应该不影响),最后得到一个metrics文件(duplicate的统计信息)和一个bam文件(duplicate的详细信息,注意要创建索引)。 123456#!/bin/bashsampleName=$1gatk MarkDuplicates -I align_bam/${sampleName}.bam -O markdup/${sampleName}.markdup.bam -M markdup/${sampleName}.markdup_metrics.txt --CREATE_INDEX truerm -rf align_bam/${sampleName}.bam # 删除中间文件 最终生成的bam文件进行下一步变异检测,可以用samtools -view 查看bam文件的内容(也没啥好看的,感兴趣可以看看之前博客写的sam文件格式解读)。 注意:官网的数据预处理后续还有一步碱基质量重校正BQSR(Base Quality Scores Recallbrate),官方提供了两个工具BaseRecalibrator和ApplyBQSR。第一个工具计算需要校正的reads和特征值,输出校准表文件,需要注意的是,这个工具需要一个或者多个已知且可靠的物种变异位点数据库,比如人类就有千人基因组计划的各种变异位点数据库,1000G_omni2.5.hg38.vcf.gz、dbsnp_146.hg38.vcf.gz等等;第二个工具根据第一个工具生成校准表,重新调整原来BAM文件中的碱基质量值,重新输出到一个新的BAM文件中。 因为我这个植物没有已知的可靠变异位点数据,因此不用做最后这一步。 本部分程序需要运行10小时。 2.2 变异检测因为我只有一个样本,所以可以按照单个样本(Single-Sample Data)的标准流程来做,也可以按照多样本(Cohort Data)的流程做。该部分主要用到的工具是HaplotypeCaller,两个流程只是对HaplotypeCaller工具产生的结果做了不同的处理,最后都是得到包含SNP和Indel的VCF文件。我也将分别跑两个流程来对比下差异。 首先要明白HaplotypeCaller这个工具具体做了什么,是怎么找出单碱基变异的: 1.定义活跃区域(Define active regions):根据是否存在变异来确定需要操作的基因组的活跃区域。 2.通过组装活跃区域确定单倍型(Determine haplotypes by assembly of the active region):对于每个活跃区域,构建一个类似De Bruijn图来重新组装活性区域,识别可能的单倍型,然后用Smith-Waterman算法将每个单倍型与参考基因组单倍型重新比对,发现潜在的变异位点。 3.确定read单倍型似然值(Determine likelihoods of the haplotypes given the read data):对每个活跃区域使用PairHMM算法对每个read与每个单倍型进行两两比对,生成单倍型似然矩阵,将似然值边缘化,以获得每个潜在变异位点的等位基因的可能性。 4.指定样本基因型(Assign sample genotypes):对每个潜在的变异位点使用贝叶斯算法,转化每个位点的基因型的似然值。然后将最可能的基因型指定为样本基因型。 以上流程翻译自HaplotypeCaller – GATK (broadinstitute.org),这个工具可用参数非常之多,下面跑的流程就展示一些常用的。 2.2.1 多样本的SNP和INDEL检测 使用HaplotypeCaller的GVCF模式,找到每个样本SNP和INDEL变异。在GVCF模式下,每个样本的结果文件以gvcf(genomic vcf)格式文件呈现,实际上gvcf格式和vcf格式类似,gvcf记录所有位点的突变情况,并且提供这些位点是否是纯和的置信度,主要还是方便将所有样本的gvcf联合起来方便分析。 123456#!/bin/bashsampleName=$1threads=50ref=/public/home/wlxie/biosoft/GATK_file/gatk/ref/luobuma.fastagatk HaplotypeCaller -R $ref --native-pair-hmm-threads ${threads} --emit-ref-confidence GVCF -I ../../pre_processing/markdup/${sampleName}.markdup.bam -O ${sampleName}.g.vcf.gz 这一步是整个SNP和IDEL检测中运行时间最长,需要算力最多的一步(主要是Pair-HMM算法花时间),Pair-HMM算法在本地调用的线程数是可以更改的,官方是给定默认值为4。GATK4.0版本开始放弃了多线程任务,这个参数可能是更新后遗漏的,因为我这里用的50线程和默认的4线程跑几乎没有区别,都是在28小时左右完成,此处参数存疑。 建库合并所有样本的GVCF文件(单样本不用做这步),并将GVCF文件转化为VCF文件。对于多样本的GVCF合并,现在官方建议用GenomicsDBImport这个工具进行建库合并,速度会快很多。 12345678#!/bin/bashsampleName=$1threads=50ref=/public/home/wlxie/biosoft/GATK_file/gatk/ref/luobuma.fasta# gatk GenomicsDBImport $(for file in `ls *.g.vcf.gz`; do echo "-V $file"; done) --genomicsdb-workspace-path database -L chr01 # 单样本不用做这步,因为就一个GVCF。多样本注意-L参数是建库必须的,根据fasta参考基因组的染色体名称命名,拆分gatk GenotypeGVCFs -R $ref -V ${sampleName}.g.vcf.gz -O raw_variants.vcf.gz # 多样本使用参数-V gendb://database 也就是上一步建的数据库名,单样本直接用gvcf文件 可以看看最后生成的VCF文件: 文件解读放在最后过滤和拆分SNP和INDEL的时候再说,这里是初步得到变异信息,需要经过过滤和筛选。 本部分程序需要运行28小时。 2.2.2 单个样本的SNP和INDEL检测使用HaplotypeCaller默认的single-sample模式,直接生成统计SNP和INDEL变异的VCF文件。 123456#!/bin/bashsampleName=$1threads=50ref=/public/home/wlxie/biosoft/GATK_file/gatk/ref/baima.fastagatk HaplotypeCaller -R $ref --native-pair-hmm-threads ${threads} -I ../../pre_processing/markdup/${sampleName}.markdup.bam -O ${sampleName}.vcf.gz 同上一个步骤,此处参数--native-pair-hmm-threads对运算速度的提升存疑,最后同样是生成VCF文件,运行时间同样为28小时。 2.3 变异过滤(优化)变异集过滤方法主要有两种: 1.软过滤:基于机器学习的方法,对原始vcf文件进行变异质量重矫正和过滤。比如有基于卷积神经网络CNN的CNNVariantTrain(有预训练的模型1D和2D),VariantRecalibrator、ApplyVQSR等可以用已知的人类变异数据集作为训练集,检测得到的SNP和INDEL的准确性(官方推荐用于人类变异过滤的方法,Variant Quality Score Recalibration,VQSR)。缺点显而易见,需要已知的真实变异数据集,除人类以外大多数生物都没有这方面的数据集。如果是研究人类基因组的话,可以从GATK官网资源处下载。 2.硬过滤:通过对6个指标的硬性阈值筛选质量合格的SNP和INDEL。 记录下硬过滤的6个指标,有些说明看不懂干脆都放英文了,参考自官网Hard-filtering germline short variants: (1)QualByDepth (QD):This is the variant confidence (from the QUAL field) divided by the unfiltered depth of non-hom-ref samples. 变异置信度,官方建议过滤该值小于2的变异。 (2)FisherStrand (FS):This is the Phred-scaled probability that there is strand bias at the site. (3)StrandOddsRatio (SOR):This is another way to estimate strand bias using a test similar to the symmetric odds ratio test. (4)RMSMappingQuality (MQ):This is the root mean square mapping quality over all the reads at the site.比对reads质量的平方根。 (5)MappingQualityRankSumTest (MQRankSum):This is the u-based z-approximation from the Rank Sum Test for mapping qualities. (6)ReadPosRankSumTest (ReadPosRankSum):This is the u-based z-approximation from the Rank Sum Test for site position within reads. 看不懂没关系,官方给出了6个硬过滤指标在SNP和INDEL中的阈值设置,详情可以看Hard-filtering germline short variants – GATK (broadinstitute.org)。以下例子均以官方的硬过滤指标为准,感兴趣可以去官网看各个参数的作用或者自己微调。 SelectVariants工具用于从vcf文件中提取SNP和INDEL信息,VariantFiltration工具用于硬过滤筛选: 123456789101112131415161718#!/bin/bashsampleName=$1threads=50VARIANTS=/public/home/wlxie/biosoft/GATK_file/gatk/variants_discover/luobuma/raw_variants.vcf.gz# SNP筛选、过滤和提取gatk SelectVariants -select-type SNP -V $VARIANTS --restrict-alleles-to BIALLELIC -O ${sampleName}_SNP.vcf.gz # BIALLELIC 限制双等位基因,不考虑其他等位多态性gatk VariantFiltration -V ${sampleName}_SNP.vcf.gz --filter-expression "QD < 2.0 || MQ < 40.0 || FS > 60.0 || SOR > 3.0 || MQRankSum < -12.5 || ReadPosRankSum < -8.0" --filter-name "Filter" -O ${sampleName}_SNP.filter.vcf.gzgatk SelectVariants -V ${sampleName}_SNP.filter.vcf.gz --exclude-filtered true -O final.${sampleName}_SNP.vcf.gz # 只显示通过过滤的变异(pass)rm -rf ${sampleName}_SNP.vcf.gz*rm -rf ${sampleName}_SNP.filter.vcf.gz*# INDEL筛选、过滤和提取gatk SelectVariants -select-type INDEL -V $VARIANTS --restrict-alleles-to BIALLELIC -O ${sampleName}_INDEL.vcf.gz # BIALLELIC 限制双等位基因,不考虑其他等位多态性gatk VariantFiltration -V ${sampleName}_INDEL.vcf.gz --filter-expression "QD < 2.0 || FS > 200.0 || SOR > 10.0 || MQRankSum < -12.5 || ReadPosRankSum < -8.0" --filter-name "Filter" -O ${sampleName}_INDEL.filter.vcf.gzgatk SelectVariants -V ${sampleName}_INDEL.filter.vcf.gz --exclude-filtered true -O final.${sampleName}_INDEL.vcf.gz # 只显示通过过滤的变异(pass)rm -rf ${sampleName}_INDEL.vcf.gz*rm -rf ${sampleName}_INDEL.filter.vcf.gz* 得到的final.sampleName_SNP.vcf.gz和final.sampleName_INDEL.vcf.gz为最终的变异集结果文件。 3. 结果文件解读因为结果文件时一个压缩过后的vcf文件,且vcf文件中前面带#部分的注释内容是用不到的,后面每一行代表一个变异位点信息,因此可以直直接统计行数来得到最终的SNP和INDEL的数量。 1zcat final.luobuma_SNP.vcf.gz | grep -v -P "^#" -c 用2.2.1多样本流程的SNP和INDEL结果文件为final.luobuma_SNP.vcf.gz和final.luobuma_INDEL.vcf.gz;用2.2.2单个样本流程的SNP和INDEL结果为final.luobuma_sm_SNP.vcf.gz和final.luobuma_sm_INDEL.vcf.gz。可以看到两者在统计SNP和INDEL数量上的差距非常小,说明这两个流程对单样本来说都是可以用的。 以final.luobuma_SNP.vcf.gz文件进行解读,通过less -S命令一行一条信息查看文件内容: 每列信息如下: 1.CHROM:染色体信息 2.POS:变异所在参考基因组的位置 3.ID:变异的ID,如果有参考变异集,会给出id,否则为.表示新发现的变异 4.REF:变异在参考基因组上的信息,必须为ATCGN五个之一 5.ALT:突变之后的情况,类型同上,.表示缺失 6.QUAL:突变后的质量值,质量值越高越可靠,通常只用pass的数据 7.FILTER:是否通过过滤 8.INFO:每个位点的详细信息(包括硬过滤的指标,详细可以到header里找) 9.FORMAT:格式 10.样本名(实际是前面格式的具体值) 主要解释一下第九列和第十列,就是上图中红色框框起来的部分,两列值是用冒号分隔且一一对应的,需要注意的值是GT: GT:0表示和参考序列一致(REF allele),1表示和样本序列一致(ALT allele),双等位基因只有0和1,0/1和0|1表示杂合,1/1和1|1表示纯和。“|”和“/”区别是前者是phased genotype,就是知道REF/ALT allele是来自于父本还是母本,在这里对我这个植物基因组没有什么意义,全都统计进杂合和纯和SNP个数就行。 AD:REF和ALT allele的覆盖度,在二倍体是是用逗号分割的两个值表示,前一个代表参考基因组的基因型,后者代表样本基因型。 DP:样本中该位点覆盖度,AD两个数字的和。 3.1 杂合率统计分别统计SNP和INDEL文件中杂合单碱基变异的个数: 1234zcat final.luobuma_SNP.vcf.gz | grep -c "0/1"zcat final.luobuma_SNP.vcf.gz | grep -c "0|1"zcat final.luobuma_INDEL.vcf.gz | grep -c "0/1"zcat final.luobuma_INDEL.vcf.gz | grep -c "0|1" 杂合率 = (杂合SNP数 + 杂合INDEL数) / 基因组大小 杂合SNP数(Hetero SNP) 杂合INDEL数(Hetero Indel) 基因组大小(bp) 杂合率 1,233,471 287,207 230,888,863 0.66% 此处计算的杂合率可以和前面做的基因组Survey做个比较,说明基因组Survey的可靠性。 3.2 单碱基准确度计算我们知道测序过程中不可避免地存在错误,三代测序数据单碱基变异的来源,包括真实的单碱基变异和测序错误导致的单碱基变异。当测序错误导致的单碱基变异存在于参考基因组上时,利用二代测序数据进行单碱基变异的检测时,会将其识别为纯合单碱基变异。因此,可以将三代数据组装的最终版基因组作为参考基因组,利用二代数据将纯合子单碱基变异率作为组装结果的错误率,即: 组装结果的准确率 = 1 - 纯合子单碱基变异率 1234zcat final.luobuma_SNP.vcf.gz | grep -c "1/1"zcat final.luobuma_SNP.vcf.gz | grep -c "1|1"zcat final.luobuma_INDEL.vcf.gz | grep -c "1/1"zcat final.luobuma_INDEL.vcf.gz | grep -c "1|1" 纯和SNP数(Homo SNP) 纯和INDEL数(Homo Indel) 基因组大小(bp) 纯和子单碱基变异率 组装结果准确率 4,176 8,580 230,888,863 0.005525% 99.994475% 这种单碱基准确度的计算结果也可以作为基因组组装质量的评估指标之一,即序列一致性评估——利用高质量的二代测序数据来评估三代测序数据组装结果在单碱基水平上的准确性。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"GATK","slug":"GATK","permalink":"http://www.shelven.com/tags/GATK/"}]},{"title":"0基础学习基因组三代测序组装(8)——基因组组装质量评估(QUAST)","slug":"0基础学习基因组三代测序组装(8)——基因组组装质量评估(QUAST)","date":"2023-03-02T15:24:41.000Z","updated":"2023-03-04T02:58:11.000Z","comments":true,"path":"2023/03/02/a.html","link":"","permalink":"http://www.shelven.com/2023/03/02/a.html","excerpt":"接上一篇博客,这一篇博客继续介绍一个常用的评估基因组组装质量的软件——QUAST","text":"接上一篇博客,这一篇博客继续介绍一个常用的评估基因组组装质量的软件——QUAST 1. QUAST介绍QUAST(Quality Assessment Tool for Genome Assemblies)是一个比较综合的评估基因组组装质量的软件,主要包括四种分析工具: QUAST:常规基因组组装质量评估 MetaQUAST:宏基因组(元基因组)组装质量评估 QUAST-LG:大型基因组组装质量评估 Icarus:Contig比对可视化工具(类似IGV浏览器的感觉) QUAST用到的软件如下(参考自国家微生物科学数据中心): 序列比对:Minimap2 基因和功能:GeneMarkS、GeneMark-ES、GlimmerHMM、Barrnap和BUSCO 查找结构变异:BWA、Sambamba 覆盖度计算:bedtools MetaQUAST:MetaGeneMark、Krona tools、BLAST和SILVA数据库 QUAST-LG:KMC和Red 这个软件优点是可以使用参考基因组或者无参考基因组情况对组装的基因组进行评估,可以快速进行大批量的基因组组装质量比较,最终的结果有图表、excel和latex等多种表现形式,也有个可以交互的网页结果,非常直观和方便。 2. 安装如果从官网下载的话,需要安装非常多的依赖软件,好消息是:可以conda安装 12345conda install -c bioconda quastquast-download-busco quast-download-gridss # 检测基因组重排的软件quast-download-silva # 著名的16s数据库,提供最新的核糖体大小亚基rRNA注释信息 截至2023年3月2日,最新版本为5.2.0 后续需要安装什么软件都可以conda search一下,能省好多功夫。注意一下conda安装之后会提醒缺两个工具和一个数据库,直接运行命令下载即可。 3. 运行实例以我的植物基因组跑一个常规基因组组装质量评估的例子: 1234#!/bin/bash#SBATCH -n 50quast -t 50 -o quast_baima -1 /public/home/wlxie/clean_data/1.fq -2 /public/home/wlxie/2.fq /public/home/wlxie/NextPolish/baima_rundir/genome.nextpolish.fasta QUAST输入文件只有组装的基因组是必须的,同时也支持三代测序--pacbio、--nanopore数据,也支持二代数据输入。我这里同时输入了二代数据,因此结果文件中有组装基因组的质量评估,也有二代数据回贴组装基因组的分析数据。 4. 结果展示运行结束后的输出日志如下: 最终生成图表结果可以在report.pdf中找到,也可以看report.html,一次运行时常大约为6小时: 左边红框框起来的部分就是二代数据回比基因组的结果,mapping率高于100%说明有多比对,完美比对率(配对reads中两条序列比对上同一个参考基因组序列的比例,Properly Paired)93.45%,覆盖度(coverage)98.63%。这个比对率说明二代测序reads与组装的基因组有较高的一致性(Properly paired 90%以上,coverage 95%以上),可以进行后续的分析。 右边是contig长度累积图,横坐标从左到右contig长度依次减小,曲线越陡表明大片段越长、数量越多,也可以看到基因组组装的连续性良好。 左上角contig的具体数据,以及N50、GC含量可以在transposed_report.txt中查看,同时也提供了latex和excel格式的结果文件,非常贴心~或者可以在basic_stats文件夹中查看相应的pdf图表: Nx图横轴是Nx百分比,比如50就是N50;纵轴是contig长度。这张图也可以反映组装结果的连续性。 最后是icarus网页结果,前面说这个界面有点像IGV。。。总之就是将各个contig从长到短组装情况可视化的工具,可以拖动底下的黄色框左右移动来查看对应的contig情况。 在基因组组装质量评估方面,这个软件就可以一次给出序列一致性、组装完整性和测序覆盖均匀性评估,还是非常方便的~当然,如果你有参考基因组的话,就可以得到更多有效的评估信息。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"QUAST","slug":"QUAST","permalink":"http://www.shelven.com/tags/QUAST/"}]},{"title":"0基础学习基因组三代测序组装(7)——基因组组装质量评估(BUSCO、LAI指数)","slug":"0基础学习基因组三代测序组装(7)——基因组组装质量评估(BUSCO、LAI指数)","date":"2023-03-01T12:39:05.000Z","updated":"2023-03-03T06:30:24.000Z","comments":true,"path":"2023/03/01/a.html","link":"","permalink":"http://www.shelven.com/2023/03/01/a.html","excerpt":"通过前面的纠错和校正步骤,我们得到了组装完成的基因组序列,接下来就是进行基因组的组装质量评估。质量评估的软件和方法比较多,这里分两篇博客记录,本篇主要演示如何用BUSCO和LAI指数评价基因组组装质量。","text":"通过前面的纠错和校正步骤,我们得到了组装完成的基因组序列,接下来就是进行基因组的组装质量评估。质量评估的软件和方法比较多,这里分两篇博客记录,本篇主要演示如何用BUSCO和LAI指数评价基因组组装质量。 复习一下前面说到的contig N50,按照contig从短到长的顺序依次相加,当相加的长度达到Contig总长度的一半,最后一个Contig长度即为contig N50. contig N50是基因组组装质量的第一指标,一般来说越高越好,但是contig N50不能完全代表一个基因组组装质量的高低,比如reads的错误连接也会使contig N50变高。接下来介绍几个现在常用的评估基因组组装质量的软件和方法。 1. 保守型基因评估BUSCO(Benchmarking Universal Single-Copy Orthologs)评估是在基因含量层面上评估基因组完整性。简单来说,通过已有的直系同源数据库进行基因组比对,同源的生物之间有保守基因序列,能比对上的基因数越多说明组装的结果越靠谱。 安装过程: 1234567891011121314151617181920# 1. 源码安装(需要安装前置软件)git clone https://gitlab.com/ezlab/busco.gitcd busco/python setup.py install# 前置软件:https://biopython.org/https://pandas.pydata.org/https://jgi.doe.gov/data-and-tools/software-tools/bbtools/https://ftp.ncbi.nlm.nih.gov/blast/executables/blast+/LATESThttp://bioinf.uni-greifswald.de/augustus/https://github.com/soedinglab/metaeukhttps://github.com/hyattpd/Prodigalhttp://hmmer.org/https://github.com/smirarab/sepp/https://www.r-project.org/# 2. conda安装(推荐)conda install -c conda-forge -c bioconda busco=5.3.2 conda安装可能会比较慢,需要多试几次。实在不行就源码下载编译,不过需要下载非常多的前置软件,不同软件可能会有环境冲突问题、gcc版本问题等等(我花了大半天时间在折腾环境)。安装之后通过busco -h查看是否安装成功,如果提示缺什么软件就用conda补上(我当前环境中没有安装pandas就会有提示)。 通过busco --list-datasets可以查看当前有哪些物种的数据库,我的植物是双子叶龙胆目,这里的数据库只有真双子叶植物(eudicots)分支离的最近,因此选择这个数据库,v5版本所有单拷贝直系同源数据库网址https://busco-data.ezlab.org/v5/data/lineages/ 下载的数据库放在busco_downloads文件夹中,解压即可使用: 12nohup wget https://busco-data.ezlab.org/v5/data/lineages/eudicots_odb10.2020-09-10.tar.gz &tar -zxvf eudicots_odb10.2020-09-10.tar.gz busco的详细参数可以看官网的user guide User guide BUSCO v5.4.4 (ezlab.org) 简单讲一讲格式和能用到的参数: 123456789101112busco -i [SEQUENCE_FILE] -l [LINEAGE] -o [OUTPUT_NAME] -m [MODE] [OTHER OPTIONS]'''主要参数: -i 序列文件位置 -l 下载的同源物种保守基因数据库位置 -o 输出文件名 -m 模式,分为genome,proteins,transcriptome三种 其他参数: --cpu 设置cpu数量 --download 在线下载数据库,根据分类有"all"、"prokaryota"、"eukaryota"和"virus" (不推荐,速度慢) --offline 离线模式,不会更新数据库''' 以下是我跑的程序,大约用了1个小时: 1busco -i /public/home/wlxie/NextPolish/01_rundir/genome.nextpolish.fasta -l /public/home/wlxie/busco_soft/busco/test_data/eukaryota/busco_downloads/lineages/eudicots_odb10 -o baima -m genome --cpu 8 --offline 截至2023/02/28,真双子叶植物库有2326个保守BUSCO基因序列,比对结果文件在short_summary.specific.xxx.xxx.txt中,如下: Complete BUSCOs (C) 多少个基因完全比对上BUSCOs Complete and single-copy BUSCOs (S) 多少个基因比对上单拷贝的BUSCOs Complete and duplicated BUSCOs (D) 多少个基因比对上多拷贝的BUSCOs Fragmented BUSCOs (F) 多少个基因部分比对上BUSCOs,可能基因只是部分存在 Missing BUSCOs (M) 多少个基因没有比对上BUSCOs,可能这些直系同源基因是缺失的 从上面的数据看,组装结果还是不错的。从中也可以看到BUSCO运行的两个步骤:用metaeuk进行基因预测(真核生物可以用tBLASTn与对应的BUSCO数据库序列进行比对从而确定候选区域,然后使用 Augustus 软件进行基因结构预测,两个软件可以替代metaeuk,详细参数见官网),以及HMMER进行同源基因的比对,从而评估基因组组装的完整性。 官方还提供了相应的python程序绘制结果图(调用了R包ggplot2),先将BUSCO结果文件放到新建的文件夹,运行相应的py程序,指定工作目录即可: 12345mkdir summariescp baima/short_summary.specific.eudicots_odb10.baima.txt summariesgenerate_plot.py -wd summaries 结果图如下: 当然,有结果数据就可以自己做更好看的图了,不一定要用官方的。 2. 长末端重复序列评估2018年发表在Nucleic Acids Research上的一篇文章Assessing genome assembly quality using the LTR Assembly Index (LAI),研究者提出了一种对长末端重复序列(long terminal repeats,LTRs)评估从而评价基因组完整度的方法,并且开发了对应的分析工具LTR_retriever 具体的LTR注释我会在后续的基因组注释笔记中更新,这里暂时跳过原理和背景部分,介绍下文章中提出的评估核心——LAI指数(LTR Assembly Index,LAI),也就是长末端重复序列组装指数。raw LAI = (完整LTR-RTs长度/总LTR长度)*100,修正后,LAI = raw LAI + 2.8138 × (94 – 整个基因组LTR identity)。 以下是一个完整的LTR-RTs的结构示意图: 文章还阐明LAI独立于基因组大小、LTR-RT含量以及基因空间评估指标(如BUSCO和CEGMA)等参数,可以用于鉴定低质量的基因组区域。使用这个指标要求**基因组中完整的LTR-RTs应至少占基因组0.1%且总LTR-RTs长度至少占5%**。 文章最后给出了LAI评价基因组完整度的三个指标: 分类 LAI 举例 Draft 0 ≤ LAI < 10 Apple (v1.0), Cacao (v1.0) Reference 10 ≤ LAI < 20 Arabidopsis (TAIR10), Grape (12X) Gold 20 ≤ LAI Rice (MSUv7), Maize (B73 v4) 2.1 LTR序列预测LTR_retriever需要以LTR_finder和/或ltrharvest的LTR预测结果文件为输入,也可以整合两个软件的预测结果作为输入(或者其他符合格式的LTR结果文件),因此需要先安装并运行以上软件,我这里以文章中提到的软件和参数运行。 12345# LTR_finder、ltrharvest和LTR_retriever安装(ltrharvest是genometools软件的一部分)conda install -c bioconda ltr_finderconda install -c bioconda genometools-genometoolsconda install -c bioconda ltr_retriever LTR_finder预测LTR序列(参数均由作者给出,只有文件是自己的): 1234#!/bin/bash#SBATCH -n 10ltr_finder -D 15000 -d 1000 -L 7000 -l 100 -p 20 -C -M 0.85 /public/home/wlxie/NextPolish/01_rundir/genome.nextpolish.fasta > baima_ltrfinder.scn 参数解释: -D NUM Max distance between 5’&3’LTR, default is 20000 # 5’和3’LTR之间的最大距离 -d NUM Min distance between 5’&3’LTR, default is 1000 -L NUM Max length of 5’&3’LTR, default is 3500 # 5’和3’LTR最大长度 -l NUM Min length of 5’&3’LTR, default is 100 -p NUM min length of exact match pair, default is 20 # 完全匹配最小长度 -C detect Centriole, delete highly repeat regions # 检测中心粒,删除高度重复区域 -M NUM min LTR similarity threshold, default is 0.00, [0,1] #最小LTR相似度 ltrharvest预测LTR序列(ltrharvest参数均由作者给出,只有文件是自己的): 12345678#!/bin/bash#SBATCH -n 10mkdir indexgt suffixerator -db /public/home/wlxie/NextPolish/01_rundir/genome.nextpolish.fasta -indexname index/baima -tis -suf -lcp -des -ssp -sds -dnagt ltrharvest -index index/baima -minlenltr 100 -maxlenltr 7000 -mintsd 4 -maxtsd 6 -motif TGCA -motifmis 1 -similar 85 -vic 10 -seed 20 -seqids yes > baima_ltrharvest.scn 这里要注意下要先使用genome tools里的suffixerator创建基因组索引文件,然后才可以使用ltrharvest进行LTR预测。 创建基因组索引的参数不做解释了,可以 gt suffixerator -help 查看。稍微记录下ltrharvest参数: -minlenltr specify minimum length for each LTR,default: 100 -mintsd specify minimum length for each TSD,default: 4 -motif specify 2 nucleotides startmotif + 2 nucleotides endmotif: **** -motifmis specify maximum number of mismatches in motif [0,3],default: 4 -similar specify similaritythreshold in range [1..100%],default: 85.00 -vic specify the number of nucleotides (to the left and to the right) that will be searched for TSDs and/or motifs around 5’ and 3’boundary of predicted LTR retrotransposons, default: 60 -seed specify minimum seed length for exact repeats,default: 30 -seqids use sequence descriptions instead of sequence numbers in GFF3 output,default: no 以上两个软件以同样的LTR最小相似度0.85预测LTR,得到两个结果文件baima_ltrfinder.scn和baima_ltrharvest.scn。 2.2 LAI指数计算用上一步的输出的两个结果文件,运行LTR_retriever鉴定LTR和计算LAI指数。 1234#!/bin/bash#SBATCH -n 20LTR_retriever -genome /public/home/wlxie/NextPolish/01_rundir/genome.nextpolish.fasta -inharvest baima_ltrharvest.scn -infinder baima_ltrfinder.scn -threads 20 这一步运行结果如下: 其他文件可以后续做LTR分析用到,这里我们只要看最后一个LAI的计算结果文件: 可以看到这个结果文件中包含了整个genome和各个contig的raw LAI和LAI指数,这里就只看整个genome的LAI指数15.37,根据上面文章作者提到的分类,属于Reference级别,也就是说可以认为达到参考基因组级别。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"BUSCO","slug":"BUSCO","permalink":"http://www.shelven.com/tags/BUSCO/"},{"name":"LAI","slug":"LAI","permalink":"http://www.shelven.com/tags/LAI/"}]},{"title":"0基础学习基因组三代测序组装(6)——基因组polish","slug":"0基础学习基因组三代测序组装(6)——基因组polish","date":"2023-02-27T03:02:16.000Z","updated":"2023-02-27T03:05:01.000Z","comments":true,"path":"2023/02/27/a.html","link":"","permalink":"http://www.shelven.com/2023/02/27/a.html","excerpt":"三代基因组de novo组装后得到一系列contig,由于三代测序的错误率较高,我们需要对组装结果进行打磨(以下均用polish表示)以提高基因组的拼接指标如Contig N50,Scaffold N50。","text":"三代基因组de novo组装后得到一系列contig,由于三代测序的错误率较高,我们需要对组装结果进行打磨(以下均用polish表示)以提高基因组的拼接指标如Contig N50,Scaffold N50。 常用软件主要有Pilon、Racon,针对PacBio的有Quiver & Arrow,针对Nanopore的有NanoPolish,以及武汉希望组为NextDenovo配套开发的NextPolish等等。要注意下先进行三代测序数据矫正,再进行二代测序数据矫正,顺序不能反,因为三代数据读长长准确率低,二代读长短准确率高,利用二代测序测序数据对三代测序数据进行纠错可以将三代测序错误率降低到二代测序的水平。如果不先进行三代序列纠错,由于基因组上存在过高错误率,导致二代序列的错误比对,影响最终polish效果。 这里以前面用NextDenovo组装的植物三代基因组为例,介绍下Racon和NextPolish用法。 1. Raconracon的基本用法如下 1racon [options ...] <sequences> <overlaps> <target sequences> 需要用到三种输入文件:sequences是指用来纠错的三代基因组测序数据(后面以原始数据称呼);target sequences指需要校正的组装后的基因组数据(后面以组装基因组称呼);overlaps指回比到组装基因组的原始数据文件,其中包含了所有的overlaps,其文件格式为MHAP/PAF/SAM三种之一。 因此在使用Racon之前需要使用其他比对工具将三代数据回贴到组装基因组上,在转录组分析笔记中有介绍过相关软件,我这里用minimap2进行比对,这是专门针对三代测序数据开发的比对工具,运行速度较快。 1minimap2 -a -t 20 <target sequences> <sequences> > minimap_1.sam -a表示结果为sam格式,<target sequences>处传入组装基因组的绝对路径,<sequences>处传入原始数据的绝对路径,比对结果的sam文件命名为minimap_1.sam 1racon -t 50 <sequences> minimap_1.sam <target sequences> > racon_minimap_1.fasta 如上一次循环下来(大约3小时),得到的racon_minimap_1.fasta就是经过一次三代数据校正的组装基因组。 一般要用三代数据polish 2-4次,之后用二代数据继续校正4次左右,可以写脚本循环,需要注意racon因为要一次读入三代原始数据和回比的sam数据,内存需求量非常大,申请的内核数需要自己计算一下,否则会报内存溢出的错误(220Mb的基因组,100X测序深度,申请50个核才能跑动)。 脚本文件racon.sh如下: 1234567#!/bin/bashminimap2 -a -t 50 $1 $2 > minimap_1.samracon -t 50 $2 minimap_1.sam $1 > racon_minimap_1.fastaminimap2 -a -t 50 racon_minimap_1.fasta $2 > minimap_2.samracon -t 50 $2 minimap_2.sam racon_minimap_1.fasta > racon_minimap_2.fasta recon.slurm: 123456789#!/bin/bash#SBATCH -J recon#SBATCH -N 1#SBATCH -n 50#SBATCH -t 7200datebash racon.sh /public/home/wlxie/NextDenovo/03_rundir/03.ctg_graph/nd.asm.fasta /public/home/wlxie/luobuma/luobuma/baima_rawdata/Third_generation_sequencing/clean_filter.fqdate 1sbatch recon.slurm 可以从输出日志中看到,racon运行主要分两步,分别是校准overlap和生成共有序列(也就是去重),在生成共有序列(consensus sequence)之后再进行二代数据的纠错。 这一步的Racon检测出两个contig可能是嵌合体(chimeric),所谓嵌合contig,该contig的某段区域可能可以比对上不同的染色体,或者头尾部分可能分别属于不同的染色体。第一遍racon的时候没有检测到,第二遍racon才出现这个提示,我不确定这两个contig是否真的是嵌合体,最终还是需要Hi-C数据来验证。 我这里两次循环得到polish的结果文件racon_minimap_2.fasta,接下来用NextPolish软件继续用二代数据polish。 2. NextPolishNextPolish是武汉那希望组开发的与NextDenovo配套的基因组polish软件,支持二代短读长、三代长读长和HiFi数据进行纠错。 和之前的NextDenovo操作方法类似,首先需要准备一个input文件,写入二代数据的绝对路径到sgs.fofn: 1realpath ./1.fq ./2.fq > sgs.fofn 从doc文件夹中copy一份配置文件run.cfg,修改参数: 1234567891011121314151617[General]job_type = slurm # local, slurm, sge, pbs, lsf塔大学校集群选择slurmjob_prefix = nextPolishtask = best # all, default, best, 1, 2, 5, 12, 1212… 1,2是两个不同的短读长算法模块,5是长读长算法模块,默认bestrewrite = yesdeltmp = yes # 删除临时结果文件rerun = 3 # 重复执行polish次数parallel_jobs = 20 # 每个job线程数multithread_jobs = 5 # job数genome = /public/home/wlxie/baima_polish/racon_minimap_2.fastagenome_size = autoworkdir = ./01_rundirpolish_options = -p {multithread_jobs}[sgs_option] #optionalsgs_fofn = ./sgs.fofn # 输入文件位置(一行一条)sgs_options = -max_depth 100 -bwa # 使用bwa进行比对 长reads和HiFi的两段配置信息删除,只留下短读长sgs_options。 这个软件的优点是速度快(100线程,4次polish,220Mb的基因组,72G的二代数据量仅仅用了8小时),而且只需要提供配置和输入文件就可以到polish结束出结果,经过4次Polish结果的迭代,最终结果如下: Contig N50比一开始NextDenovo组装结果大,也就是组装效果更好。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"NextPolish","slug":"NextPolish","permalink":"http://www.shelven.com/tags/NextPolish/"},{"name":"Racon","slug":"Racon","permalink":"http://www.shelven.com/tags/Racon/"}]},{"title":"0基础学习基因组三代测序组装(5)——三代数据组装","slug":"0基础学习基因组三代测序组装(5)——三代数据组装","date":"2023-02-23T14:22:00.000Z","updated":"2023-02-28T13:07:16.000Z","comments":true,"path":"2023/02/23/a.html","link":"","permalink":"http://www.shelven.com/2023/02/23/a.html","excerpt":"前段时间比较忙,现在继续整理基因组测序组装系列的学习笔记。第四篇笔记写的二代测序基因组组装,主要是演示二代测序数据组装的主流工具SOAPdenovo 2.0是如何应用的。我这里有了二代和三代的测序数据,后续组装还是以三代数据为主,这里就继续记录下几款三代测序数据组装的主流工具和用法。","text":"前段时间比较忙,现在继续整理基因组测序组装系列的学习笔记。第四篇笔记写的二代测序基因组组装,主要是演示二代测序数据组装的主流工具SOAPdenovo 2.0是如何应用的。我这里有了二代和三代的测序数据,后续组装还是以三代数据为主,这里就继续记录下几款三代测序数据组装的主流工具和用法。 现在主流的三代测序公司是Pacbio和Nanopore,两家测序公司测序原理不同,产生的数据类型也有区别。 1. 主流三代测序平台1.1 Pacbio测序平台Pacbio测序平台是单分子实时测序(single molecule real time sequencing,SMRT),原理是当DNA与聚合酶形成的复合物被ZMW(零模波导孔)捕获后,4种不同荧光标记的dNTP随机进入检测区域并与聚合酶结合,与模板匹配的碱基生成化学键并激发荧光,生成化学键激发的荧光存在的时间远远长于其他碱基被激发荧光的时间,从而实现单碱基的实时检测。 现在Pacbio有两种测序模式,一种是CLR测序模式(超长测序模式),产生数据基于单循环测序结果;一种是HiFi测序模式,也就是高保真测序模式,产生数据基于滚环一致序列(Circular Consensus Sequencing ,CCS)。具体原理就不说了,两种测序模式中HiFi数据相对来说准确率会高一些,所以不同软件对两种测序模式的数据也会有不同的处理。 1.2 Nanopore测序平台Nanopore测序平台前面第一篇博客介绍过了,可以点击这里。需要了解ONT测序平台测序产生的原始数据是电信号,经过basecalling之后才可以转化成我们要的测序数据。 2. 三代基因组测序组装软件2.1 NextDenovoNextDenovo是武汉希望组开发的集校正、比对和组装一体的,基于字符串图(string graph-based)的三代测序基因组组装软件。它的实现过程和另一款经典的三代基因组组装软件Canu类似,经过长读长数据的纠错校正后再进行组装。 官网上介绍原来可以对CLR、HiFi和ONT数据都可以组装,HiFi数据可以跳过数据的自我纠错过程,如今HiFi数据被划掉了,也许已经不再适用,但是对Pabio的CLR和Nanopore的ONT测序数据仍有较好的组装效果,其介绍是组装的准确率有98%-99.8%。 NextDenovo主要有两个核心模块 NextCorrect和NextGraph。NextCorrect用于原始数据纠错,NextGraph用于纠错后数据的组装。据作者介绍,与其他工具相比,NextDenovo在装配一致性和单碱基装配精度方面表现出较高的水平。我个人用起来是觉得这个软件运行时间相对Canu较短,需要的算力资源较小,可以很快地组装出结果(后面可以进行比较)。 安装并测试通过后,我们就可以开始使用这个工具了。 首先准备input文件,将前面质控后的三代测序数据的绝对路径写在input.fofn文件里: 1/public/home/wlxie/luobuma/luobuma/baima_rawdata/Third_generation_sequencing/clean_filter.fq 接下来也是最重要的,修改配置文件: 123456789101112131415161718192021222324[General]job_type = slurm # local, slurm, sge, pbs, lsf塔大学校集群选择slurmjob_prefix = nextDenovotask = all # all, correct, assemble选择只进行correct还是只进行assemble,或者两者都进行,基因组小的话可以直接allrewrite = yes # yes/no覆写deltmp = yes parallel_jobs = 20 # number of tasks used to run in parallel线程数,咱学校的集群20勉强够input_type = raw # raw, corrected输入的数据情况read_type = ont # clr, ont, hifi数据类型input_fofn = input.fofn # 输入数据的位置信息workdir = 03_rundir # 输出的文件夹名字[correct_option]read_cutoff = 1k # 进行correct的时候截取的最小readgenome_size = 230m # estimated genome size预估的基因组大小sort_options = -m 20g -t 15minimap2_options_raw = -t 8pa_correction = 3 # number of corrected tasks used to run in parallel, each corrected task requires ~TOTAL_INPUT_BASES/4 bytes of memory usage.correction_options = -p 15 -dbuf # 非常重要!-dbuf让每一步作业释放内存,防止节点卡死![assemble_option]minimap2_options_cns = -t 8 nextgraph_options = -a 1 以上是我的配置文件信息,中文标注的地方都很重要,根据情况修改。其他参数可以用默认。其中预估的基因大小也是很有必要的,前面在做基因组Survey的时候预测过,这里就直接写预测的基因组大小。 其他参数的设定和使用可以参考这篇博客使用NextDenovo组装Nanopore数据,以及官方的参数说明手册NextDenovo Parameter Reference — NextDenovo latest documentation 需要强调一点,correction_options = -p 15 -dbuf这项参数是我在华农的集群平台手册上看到的,之前确实一直会卡死在某一步直到24h后台强杀这个进程,目前未知原因,加上之后运行正常。以我的数据来看,一个200多Mb的植物基因组,测序深度100X左右,一次组装运行结束需要12小时左右,已经非常快了。 最后是运行程序,我写了一个run.slurm文件: 1234#!/bin/bash#SBATCH -n 40./nextDenovo run.cfg 1sbatch run.slurm 基因组比较大的话,建议分步运行,先correct,再assemble。 因为是在集群中运行,所有输出都会在slurm-xxxx.out的文件夹中显示,打开以后可以看到每个时间节点完成了什么任务,当有任务卡住几个小时都没动的时候,就要检查是否是配置文件是否正确。 最后组装的基因组在03.ctg_graph目录下,文件名称nd.asm.fasta。最底下输出了组装结果概况,contig N50为9Mb,总共组装出225Mb的基因组序列,contig总数为59,组装结果还算不错。 2.2 CanuCanu是三代测序数据组装的经典工具,也是主要用于Pacbio和Nanopore公司的测序结果组装。 这个软件在安装过程中有点曲折,从官网下数据包,最后一步编译的过程会报错。目前我暂时没办法解决,但是可以用conda安装(虽然官网不建议这么做)。 1conda install -c conda-forge -c bioconda -c defaults canu 如果报错动态库出问题,可以参考第三篇博客中的方法,寻找根目录下的动态库中是否有对应的版本文件,如果有,直接修改软链接到对应的动态库下。 Canu运行分为三个步骤:纠错(Correct)、修剪(Trim)和组装(Assemble)。可以每一个步骤分开跑,比如纠错修剪之后的数据可以放到别的软件中组装,或者用别的软件纠错之后作为输入到Canu中组装。考虑到塔大集群24小时自动杀程序,保险起见还是三个步骤分开跑比较安全。 下面是官方的帮助文档,写的非常详细: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960usage: canu [-version] [-citation] \\ [-haplotype | -correct | -trim | -assemble | -trim-assemble] \\ [-s <assembly-specifications-file>] \\ -p <assembly-prefix> \\ # 输出文件前缀 -d <assembly-directory> \\ # 输出目录 genomeSize=<number>[g|m|k] \\ # 预测基因组大小 [other-options] \\ [-haplotype{NAME} illumina.fastq.gz] \\ [-corrected] \\ [-trimmed] \\ [-pacbio | -nanopore | -pacbio-hifi] file1 file2 ...example: canu -d run1 -p godzilla genomeSize=1g -nanopore-raw reads/*.fasta.gz To restrict canu to only a specific stage, use: # 描述canu要执行的主程序 -haplotype - generate haplotype-specific reads -correct - generate corrected reads -trim - generate trimmed reads -assemble - generate an assembly -trim-assemble - generate trimmed reads and then assemble them Reads can be either FASTA or FASTQ format, uncompressed, or compressed with gz, bz2 or xz. Reads are specified by the technology they were generated with, and any processing performed. [processing] # 描述reads状态 -corrected -trimmed [technology] # 描述测序平台(数据类型) -pacbio <files> -nanopore <files> -pacbio-hifi <files> Some common options: useGrid=string - Run under grid control (true), locally (false), or set up for grid control but don't submit any jobs (remote) rawErrorRate=fraction-error # 降低这个参数会提高第一步的速度 - The allowed difference in an overlap between two raw uncorrected reads. For lower quality reads, use a higher number. The defaults are 0.300 for PacBio reads and 0.500 for Nanopore reads. correctedErrorRate=fraction-error # 降低这个参数可以提高组装效率 - The allowed difference in an overlap between two corrected reads. Assemblies of low coverage or data with biological differences will benefit from a slight increase in this. Defaults are 0.045 for PacBio reads and 0.144 for Nanopore reads. gridOptions=string - Pass string to the command used to submit jobs to the grid. Can be used to set maximum run time limits. Should NOT be used to set memory limits; Canu will do that for you. minReadLength=number - Ignore reads shorter than 'number' bases long. Default: 1000. minOverlapLength=number - Ignore read-to-read overlaps shorter than 'number' bases long. Default: 500. A full list of options can be printed with '-options'. All options can be supplied in an optional sepc file with the -s option. # 可以用-s来提供自己修改的参数文件Complete documentation at http://canu.readthedocs.org/en/latest/ 也可以点击这里,进官方手册看原文。下面用我自己的三代数据跑一个案例。 2.2.1 纠错(Correct)因为三代测序数据错误率较高,纠错的步骤是通过序列之间的一致性比较获得高可信的碱基。 创建一个canu的空文件夹,写入以下内容到correct.slurm: 12345#!/bin/bash#SBATCH -N 1#SBATCH -n 40canu -correct -p baima -d baima_nanopore genomeSize=230m -nanopore-raw /public/home/wlxie/luobuma/luobuma/baima_rawdata/Third_generation_sequencing/pass.fq 1sbatch correct.slurm 主要就是注意下参数,-p是输出文件的前缀,-d是输出文件的目录名,需要声明这个数据是什么平台测的,以及数据是什么状态。虽然我这里只申请了40个核,但是canu会自动提交作业直到你能申请的核数上限……在塔大集群我的权限是200个核,通过scontrol show job 可以查到,我这边一次性提交了136个作业,排队100多个任务,占用192个核….. 纠错、修整和组装每一个步骤会依次进行以下各个阶段,需要的内存和核数挺高的,所以推荐在集群中运行。 如果运行时间很长,建议到设置的输出文件夹目录下查看canu.out文件,详细记载了正在执行哪一步以及花了多少时间。如果不确定程序是否卡死,直接通过scontrol show job命令查看状态为RUNNING的作业,进入作业的输出目录,如果文件夹中的内容一直到最近的时间点都有更新,则可以放心地继续运行。 仅仅这一个步骤花了30个小时。并且这一步会将100X的测序数据量降到40X(默认,可以调整,见官方readSampleingCoverage参数介绍)。最后生成文件baima.correctedReads.fasta.gz,我为了方便复制到了前一个文件夹,修改文件权限为0755。 2.2.2 修整(trim)修整是在上一步纠错的基础上,再对reads进行修剪,删去可疑区域。 这一步经历的步骤与上一步是一样的,虽然上一步纠错已经将数据量降了一大半,对于我的基因组,这一步依然要跑32小时. 同样在canu文件夹写入如下内容到trim.slurm: 12345#!/bin/bash#SBATCH -n 50#SBATCH -t 7200canu -trim -p baima -d baima_trim genomeSize=230m -corrected -nanopore /public/home/wlxie/canu/baima.correctedReads.fasta.gz 1sbatch trim.slurm 注意修改参数以及reads所处的状态。 在canu.out输出文件中,可以找到trim步骤用了什么参数,处理了哪些类型的reads: 生成的结果文件在baima_trim文件夹中,名称为baima.trimmedReads.fasta.gz,同样修改文件权限,移到前一个文件夹方便操作。 2.2.3 组装(Assemble)经过前两步的数据纠错和修整,这一步才是正式组装基因组。 在canu文件夹写入如下内容到assemble.slurm: 12345#!/bin/bash#SBATCH -n 50#SBATCH -t 10800canu -assemble -p baima -d baima_assemble genomeSize=230m correctedErrorRate=0.144 -trimmed -corrected -nanopore /public/home/wlxie/canu/baima.trimmedReads.fasta.gz 1sbatch assemble.slurm 在官方的介绍中,correctedErrorRate这个参数可以根据前面纠错和修整的reads质量做修改的,默认是Pacbio数据0.045,Nanopore数据0.144,降低这个参数值可以加快组装的效率,但是存在遗漏overlap和组装片段断裂的风险。低于30X测序深度以下的数据,官方建议可以略微提高这个值,对于60X测序深度以上的数据可以略微降低这个值,每次改变1%左右比较合适。 我这里就用默认参数了,组装时间可能会比较长,就将作业的时间调整为7天。 实际运行时间为30小时,最终组装结果如下: 这个工具组装出的结果比预期大很多,前面基因组survey预测的基因组大小为230Mbp,实际通过canu组装出有276Mbp,且contig数明显比NextDenovo多,导致contig N50指标低。我觉得可能是correctedErrorRate这个值比较高,可以适当调低一些,过于严格的纠错标准可能导致组装的contig比较碎。 因为前面的NextDenovo组装的效果已经比较理想,因此这一步我也就不再细调参数了。以NextDenovo组装出的基因组继续后面的分析。以目前我的植物基因组来看,用NextDenovo组装三代基因组的效率和质量都比Canu高。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"NextDenovo","slug":"NextDenovo","permalink":"http://www.shelven.com/tags/NextDenovo/"},{"name":"Canu","slug":"Canu","permalink":"http://www.shelven.com/tags/Canu/"}]},{"title":"解决github DNS污染的三种方法","slug":"解决github-DNS污染的三种方法","date":"2023-02-21T13:47:13.000Z","updated":"2023-02-21T13:49:36.000Z","comments":true,"path":"2023/02/21/a.html","link":"","permalink":"http://www.shelven.com/2023/02/21/a.html","excerpt":"最近因为需要安装各种生信软件,用github比较多,每次登github都要翻墙很是麻烦。索性花了点时间研究了一下,下面分别列举三种情况下的解决github网站登录的问题。","text":"最近因为需要安装各种生信软件,用github比较多,每次登github都要翻墙很是麻烦。索性花了点时间研究了一下,下面分别列举三种情况下的解决github网站登录的问题。 知其然知其所以然,首先我们需要明白为什么github在国内经常无法登录。我们在浏览器地址栏输入网址以后,域名服务器会对输入的域名进行解析(DNS解析),解析成为计算机之间可以识别的ip地址。然而因为一些众所周知的因素,很多国外的网站在国内是无法直接访问的,其中一个限制方法就是DNS污染,将域名服务器中缓存的域名指向不正确的ip地址。这种限制手段就和你公司电脑会限制你浏览一些网站是一样的。 因此要浏览这些被DNS污染的网站,我们需要跳过受污染的局域域名服务器,常用的方法就是代理服务器和VPN(VPN就是一个典型的正向代理),通过更远的服务器转发我们的http请求,在经过未污染的域名服务器解析之后,返回我们想要的网页内容。当然,如果仅仅只是用翻墙的方法的话,本篇博客这里可以结束了,下面通过三种情况分别讲一下如何通过修改host等其他方法访问github。 1. Windows系统修改host首先注意一点,如果你正在用翻墙软件或者VPN等,需要先将所有代理都关闭(防止全局代理导致设置的host失效)。 host文件是一个没有扩展名的系统文件,可以用notepad++打开编辑,其本质就是就是将访问的域名和ip地址建立关联。当浏览器中输入网址时,系统会首先从host文件中找到域名对应的ip地址,如果没有找到才会发送给域名解析器。所以我们只需要找到github相关域名对应的正确的ip地址,即可正常访问github(其他受DNS污染的网址同理)。 打开网址 https://www.ipaddress.com/ 主要查找以下github相关域名的ip地址,有多少个记录多少个 123456github.com # 主站nodeload.github.com api.github.com # APIcodeload.github.comgithub.global.ssl.Fastly.net # git clone速度相关assets-cdn.github.com # 静态资源相关 windows系统中host文件位置 C:\\WINDOWS\\System32\\drivers\\etc\\hosts notepad++打开host文件,最后加入如下查到的ip地址,保存 12345678910111213141516# domain: github.com# 更新时间 2023年2月21日140.82.113.3 github.com140.82.114.10 nodeload.github.com140.82.112.6 api.github.com140.82.114.10 codeload.github.com151.101.1.194 github.global.ssl.Fastly.net151.101.65.194 github.global.ssl.Fastly.net151.101.129.194 github.global.ssl.Fastly.net151.101.193.194 github.global.ssl.Fastly.net185.199.108.153 assets-cdn.github.com185.199.110.153 assets-cdn.github.com185.199.111.153 assets-cdn.github.com 最后一步打开cmd命令行,刷新缓存就可以正常登录github了 ipconfig /flushdns 2. Linux系统修改host方法步骤同上,需要注意linux系统修改host需要root权限! linux系统host文件路径 /etc/hosts 3. 无root权限的linux系统访问github有些时候我们会在集群中安装软件,这个时候是没有root权限的,无法通过修改host的方法直接访问,因此也无法用git clone的方法克隆仓库(会报错,错误代码443)。 今天碰到这个问题,找贺师兄聊了聊才发现可以找镜像资源曲线救国……突然打开了新世界的大门hhhh 真的解决了困扰我很久的疑惑,按照往常我只能翻墙下载源代码,再解压后传回服务器,一来一回校园网的速度要传很久很久…… 具体做法是先下载油猴(Tampermonkey)插件,这是个非常有名的脚本管理器,下载安装方式就不说了,网上一大把,自己搜下github官方也非常简单。Tampermonkey/tampermonkey 然后是安装github增强加速插件,插件地址 https://greasyfork.org/zh-CN/scripts/412245-github-%E5%A2%9E%E5%BC%BA-%E9%AB%98%E9%80%9F%E4%B8%8B%E8%BD%BD 我因为安装过了,这里就只演示一下: 安装之后再回到原来github页面,点击code按钮,就可以看到原来只有一个git clone地址,现在有好几个地址给出来了,在集群里随便git clone选择其他地址,就可以在成功下载啦。 这个javascript脚本感兴趣的话可以在油猴中看看,本质上也是个CDN加速和代理,只不过用的都是公益资源,速度也相当不错了。 4. 写在最后前面两个方法改host并不是一劳永逸的,隔一段时间github的ip地址就会更新(不定,可能几天,可能几周)。这种纯手动更新的方法仍然是不够智能不够优雅的,有能力的话以后写一个自动更新的脚本(间隔一段时间自动查找相关github域名的ip地址,自动更新到host文件中)。 目前为止,还是代理最为省心。","categories":[{"name":"网络相关","slug":"网络相关","permalink":"http://www.shelven.com/categories/%E7%BD%91%E7%BB%9C%E7%9B%B8%E5%85%B3/"}],"tags":[{"name":"github","slug":"github","permalink":"http://www.shelven.com/tags/github/"}]},{"title":"校园网代理服务器的搭建","slug":"校园网代理服务器的搭建","date":"2023-02-09T08:55:45.000Z","updated":"2023-02-09T08:58:49.000Z","comments":true,"path":"2023/02/09/a.html","link":"","permalink":"http://www.shelven.com/2023/02/09/a.html","excerpt":"以前的一篇博客讲过如何搭建反向代理服务器,从而实现在校外登录校内集群,详情点击这里。本篇博客主要记录下如果想要在校外登录学校教务平台、登录学校购买的数字资源库应该如何实现。","text":"以前的一篇博客讲过如何搭建反向代理服务器,从而实现在校外登录校内集群,详情点击这里。本篇博客主要记录下如果想要在校外登录学校教务平台、登录学校购买的数字资源库应该如何实现。 1. 实现思路首先要更正一下前一篇博客的错误之处,学校集群是有公网ip的,只是限制了ip访问。 使用的工具仍然是frp,需要的设备是一台在校内24h不断电的服务器(本篇博客中的校内集群),以及一台有公网ip的校外服务器(本篇博客中是我的轻量云服务器)。 上篇内网穿透博客记录的是典型的反向代理过程,将校内集群ssh登录的端口映射到校外服务器的其他端口,通过访问该端口登录校内集群,整个代理过程客户端无法得知服务端的真实ip和端口,可以做到隐藏服务端真实信息、确保服务端安全。 而我们这里想要登录校园内的其他网站、使用校园网ip登录知网万方等数据库的话,就需要用校内集群转发我们的http请求,将请求返回的结果通过校外服务器中转后返回给我们,按照我的理解,这是一个正向代理+反向代理结合的过程。 反向代理:隐藏了服务端,我并不知道我访问的实际是校内集群(输入的是校外服务器的ip地址和端口)。 正向代理:隐藏了客户端,集群将http请求转发到校内网站和学校买的数据库网站(后者并不知道发出请求的实际是校外服务器)。 校内集群在整个过程中信息完全被隐藏,校外服务器起到中转的作用,最后将http请求返回的结果传递给我们。 2. frp配置2.1 service端配置frps安装在校外服务器上,frps.ini配置文件完全不用修改,这里就顺便展示一下 123456789101112131415[common]bind_port = 7000 # frp监听的端口,默认7000,可改bind_udp_port = 7400 # UDP通讯端口,可不设置,用于点对点穿透token = xxxxxxxx # 安全考虑需要设置口令,client端需要用到dashboard_port = 7500 # frp管理端口,可改dashboard_user = xxxx # 管理端口认证的用户名,用于身份识别,自己设置dashboard_pwd = xxxx # 管理端口认证的密码,用于身份识别,自己设置enable_prometheus = truesubdomain_host = xxx.xxx.xxx # 设置子域名,主要方便登录管理界面。不用ip地址,用域名+端口的方式直接访问log_file = /usr/local/frp/frps.log # frp日志配置,这里是记录3天的日志信息log_level = infolog_max_days = 3 2.2 client端配置frpc安装在校内集群,frpc.ini配置文件修改如下 123456789[common]server_addr = xxx.xxx.xxx.xxx # 填写你的service端服务器公网ip,这里我写我的云服务器ipserver_port = 7000 # 前面设置的frp监听端口,需要保持一致token = xxxxxxxxx # 前面设置的口令[http_proxy] # 这里只演示http代理,有ssh需求的自行加入,其他参考frpc_full.initype = tcpremote_port = xxxx # 映射的service端服务器的端口,自己定义plugin = http_proxy # http代理插件(frpc自带) 2.3 开放端口,开启frp服务service端(也就是校外服务器)开放上一步client端设置的服务器端口,重载防火墙。 分别在service端和client端后台不挂起运行frps和frpc(对应文件夹中运行) 12nohup ./frps -c frps.ini &nohup ./frpc -c frpc.ini & service端可以查看运行日志frps.log文件,端口监听成功即可;client端可以查看nohup文件的运行结果(最好定时清一下,否则这个文件会很大)。 如果想要停止frp服务,查看任意service端或者client端的frp程序进程,结束即可 12ps -aux | grep frp # 查看任务进程号kill -9 进程号 # 结束任务进程 3. 代理实现按照上面步骤搭建好代理服务器,我们只需要在电脑上设置代理地址和端口,就可以在电脑上用外网访问校园内网和数据库网站了。 上面的图片我就不放真实信息了,根据图片意思设置即可,一般来说教程到这里就可以结束了,但是仅仅如此还不够优雅。 如果不对地址进行限制,那么所有http访问都将通过代理进行,即全局代理,一般情况下我们是不希望如此的。举个例子,如果访问的是localhost,我们一般是用直连(Direct);访问限制ip的特定的网站走代理服务器(Proxy);访问不限制ip的网站仍然用直连,因为直连速度最快,只受你电脑带宽限制。 如果访问所有网站都用代理的话,比如我的小破服务器就1M带宽,速度就很感人了…… 简单讲一讲三种方法优雅地实现校园网代理 3.1 使用设置脚本没错就是手动设置代理上面那个选项,这个脚本是以**.PAC为扩展名的JavaScript脚本**,决定http请求是通过直连目标还是通过代理的方式连接。 pac文件中使用的JavaScript 函数可以在官方查到用法,这里做个示例: 12345678910111213141516171819function FindProxyForURL(url, host) { //设置代理池 var proxy1 = "PROXY xxx.xxx.xxx.xxx:xxxx"; var proxy2 = "PROXY xxx.xxx.xxx.xxx:xxxx"; //本地地址直连 if (isPlainHostName(host)) { return "DIRECT"; } // 代理1 if (shExpMatch(url, "*.cnki.com/*")) { return proxy1; } // 代理2 if (shExpMatch(url, "*.wanfangdata.com.cn/*")) { return proxy2; } return "DIRECT";} 将上面的文件保存为.pac格式文件,代理池部分填写前面做的代理服务器ip和端口(当然我只有一个代理服务器,根据需要自己改),需要代理的网址这里可以用shExpMatch函数进行正则匹配,完成后再设置自动设置代理部分。打开自动检测设置和使用设置脚本,将.pac文件的地址(可以是本地地址或者放在你自己的服务器上,能访问到就行)贴到脚本地址栏。 接下来就可以愉快地访问学校购买的数字资源啦并且上其他网站因为是直连网速也不会变慢 3.2 使用其他代理软件代理软件种类繁多,相比直接设置脚本,代理软件往往还提供更多更直观的方式控制http代理方式,这里就简单介绍个软件Proxifier 设置方法与手动设置大同小异(手动设置的代理规则比较反人类),同样是设置代理服务器ip,端口,可以设置不同的协议,还可以启用验证保证安全。 设置代理规则 代理规则就不一一细说了,这个软件就是比较直观,而且还可以记录代理产生的流量等等。 3.3 做个切换全局代理工具如果是自己用的话,使用代理脚本是最简单最优雅又不费事的方法。 如果要给同一个实验室其他人分享,又怕别人电脑不安全(直接分享脚本会导致源码泄露,万一别人电脑被黑了自己服务器的信息就被暴露了),就可以做一个代理工具,别人用得着的时候开启,用不着的时候就关闭(全局代理的重要性,就算忘记关闭代理,看到别的网页速度变慢了,也就会想起来去关闭代理了),可以一定程度上保护自己服务器的安全,又简化别人设置代理的步骤,一举两得哈哈~ 实现方式就是做一个批处理,然后将bat转化为exe即可 1234567891011121314151617181920212223242526272829303132333435363738394041424344@echo offfor /f "tokens=1,2,* " %%i in ('REG QUERY "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings" /v ProxyEnable ^| find /i "ProxyEnable"') do (set /A ProxyEnableValue=%%k)if %ProxyEnableValue% equ 0 ( echo 正在开启知网代理,请稍候... ping -n 2 127.0.0.1>nul echo= echo 获取注册表中代理启用状态...... reg add "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings" /v ProxyEnable /t REG_DWORD /d 1 /f ping -n 2 127.0.0.1>nul echo= echo 设置代理服务器...... reg add "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings" /v ProxyServer /d "xxx.xxx.xxx.xxx:xxxx" /f ping -n 2 127.0.0.1>nul echo= echo 代理已开启,请阅读弹窗内容并按确认键关闭本窗口... echo= echo 知网一键代理工具 Version 1.0 echo 版权所有 塔里木大学研发中心405. 保留所有权利。 echo 该工具由405实验室内部开发,仅供本实验室人员使用,切勿外传。 echo msgbox"代理开启期间网速会变慢,使用完毕后请再次点击该工具结束代理!",0,"提示"> %tmp%\\\\tmp.vbs cscript /nologo %tmp%\\\\tmp.vbs del %tmp%\\\\tmp.vbs) else if %ProxyEnableValue% equ 1 ( echo 正在关闭知网代理,请稍候... ping -n 2 127.0.0.1>nul echo= echo 获取注册表中代理启用状态...... reg add "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings" /v ProxyEnable /t REG_DWORD /d 0 /f ping -n 2 127.0.0.1>nul echo= echo 清除代理服务器设置...... reg add "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings" /v ProxyServer /d "" /f ping -n 2 127.0.0.1>nul echo= echo 代理已关闭,请按确认键退出本窗口... echo= echo 知网一键代理工具 Version 1.0 echo 版权所有 塔里木大学研发中心405. 保留所有权利。 echo 该工具由405实验室内部开发,仅供本实验室人员使用,切勿外传。 echo msgbox"再见。",0,"提示"> %tmp%\\\\tmp.vbs cscript /nologo %tmp%\\\\tmp.vbs del %tmp%\\\\tmp.vbs) 上面的内容文件保存为.bat后缀文件(有中文的话,保存时编码格式要改为ANSI),只需修改xxx.xxx.xxx.xxx:xxxx部分为你前面设置的代理服务器ip和端口即可。 原理就是获取注册表中代理服务的开启状态,转化为另一种状态, ping -n 2 127.0.0.1这个只是为了使用者有参与感……延迟两秒进行下一步处理hhhhhh 最后网上找个图,转成icon格式作为图标,然后将bat批处理格式文件转化成exe可执行程序文件即可,我这里用的Bat_To_Exe_Converter这个软件,转成exe源码不容易被泄露,而且怎么说呢看上去给人感觉也稍微正式一点(bushi)。 大概就是这样,每双击一次应用程序,切换全局代理的状态为开或者关 4. 注意浏览器是会缓存ip信息的,无论使用何种方式开启代理,最好都先关闭浏览器之后重新打开。","categories":[{"name":"网络相关","slug":"网络相关","permalink":"http://www.shelven.com/categories/%E7%BD%91%E7%BB%9C%E7%9B%B8%E5%85%B3/"}],"tags":[{"name":"内网穿透","slug":"内网穿透","permalink":"http://www.shelven.com/tags/%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F/"},{"name":"代理服务器","slug":"代理服务器","permalink":"http://www.shelven.com/tags/%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8/"}]},{"title":"基于requests和Xpath改进微信公众号爬虫","slug":"基于requests和Xpath改进微信公众号爬虫","date":"2022-12-18T15:00:18.000Z","updated":"2022-12-18T15:03:38.000Z","comments":true,"path":"2022/12/18/a.html","link":"","permalink":"http://www.shelven.com/2022/12/18/a.html","excerpt":"前面一篇博客讲了requests、Xpath和selenium的用法,最后用selenium模拟浏览器对搜狗微信文章做了自动化爬取。从搜狗微信网页爬取的公众号文章其实是不全的,不能保证公众号的所有文章都被搜狗收录,且selenium爬取速度相对较慢(但是对动态页面爬取很有用),因此可以选择另一种方式——直接从微信公众号后台进行爬取。","text":"前面一篇博客讲了requests、Xpath和selenium的用法,最后用selenium模拟浏览器对搜狗微信文章做了自动化爬取。从搜狗微信网页爬取的公众号文章其实是不全的,不能保证公众号的所有文章都被搜狗收录,且selenium爬取速度相对较慢(但是对动态页面爬取很有用),因此可以选择另一种方式——直接从微信公众号后台进行爬取。 这两天改了下代码,就讲一讲从微信公众号后台爬文章的思路。 1. 准备工作首先是申请微信公众号,自从2018年微信公众号加强用户管理以后,一个身份证只可以注册一个订阅号了,除非你有营业执照,以公司为主体注册名额还能加两个。比较建议多弄几个微信公众号,只要绑定自己是运营者就行,可以让朋友帮忙注册一下,从微信公众号后台直接爬是有可能被ban接口的,被反爬机制检测到第一次ban一小时,第二次可能ban一天,看情况而定。 我这里是准备了三个微信公众号,保证爬取过程不中断~ 首先进入微信公众号后台,点击图文消息,在跳转的编辑页面上方点击超链接。 在这个页面按F12进入开发者工具,链接内容选择其他公众号,输入你想要爬的公众号名字,点击右边放大镜搜索后对返回数据抓包。 这里第一个返回的数据包是显示公众号搜索内容的,一个重要的参数fakeid就是公众号名字的内部编号。然后返回前面的标头,获取cookie,这是我们登录微信公众号的凭证,后面爬取网页必须带上cookie内容。 点击我们要找的公众号(你名字输对的话肯定是第一个),又返回一个数据包,在负载里我们可以看到begin和count两个重要的参数。在试验过后可以发现,begin表示从哪一页开始,count表示一页显示多少天的推送,这里count值在我多次试验之后,发现最大值为5,传入超过5的数都会变成默认值5(也就是说不能通过一页获取所有文章的url)! 而在响应体中,我们可以看到所有返回文章的title、link、update_time、digest等重要的信息都在app_msg_list中,上面的app_msg_count值我测试后发现是记录一共发布文章天数的。 在这个公众号例子中,digest本来应该是摘要的,但在这里只是一段甚至半段内容,无法提取有用的信息,所以我直接忽略了这部分数据;而且一般公众号会把subtitle分离出来,这个公众号没有,因此需要写一段代码分离标题中的分类标题,以标题中的竖线来分割“副标题(分类)|标题”。 接下来可以随便点一个link,F12看看文章的html结构,记录文章内容的xpath地址(不记得xpath地址怎么找的话,看前一篇xpath用法)。这里文章的图片我就没有收集了,我只收集了文字部分内容。 每个公众号排版不一样,根据内容再写一个正则匹配一下不需要的内容,也就是对内容“去噪”。不详细讲,因公众号而异,我这里要去除的噪声是公众号底部的进群邀请内容。 2. 代码部分截至目前为止(2022年12月28日),代码运行正常: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174import randomimport timeimport requests, refrom requests.packages import urllib3from lxml import etreeimport xlwtfrom pandas import DataFramekey_word = "植物生物技术Pbj"xpath_string = '//*[@id="js_content"]//text()' # 文章内容的xpath路径last_date = 2018 # 想要获得哪一年之后的文章# 创建工作表格,存储爬取的临时数据book = xlwt.Workbook(encoding='utf-8',style_compression=0)sheet = book.add_sheet(key_word,cell_overwrite_ok=True)col = ('title', 'author', 'content','category',"link","date")for i in range(0,6): sheet.write(0,i,col[i]) # 第一行写入属性名称,write对应参数:行、列、值urllib3.disable_warnings() # 忽略警告# 最终爬取数据存放列表title_list = []link_list = []cat_list = []date_list = []content_list = []author_list = []s = requests.Session() # 维持会话# 对微信公众号查找的headersheaders = { 'User-Agent': "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:72.0) Gecko/20100101 Firefox/72.0", "Host": "mp.weixin.qq.com", 'Referer': 'https://mp.weixin.qq.com/'}cookie_str = ""cookies = {}# 加载cookies,将字符串格式的cookies转化为字典形式def load_cookies(): global cookie_str, cookies for item in cookie_str.split(';'): sep_index = item.find('=') cookies[item[:sep_index]] = item[sep_index + 1:]# 去噪函数,只适合该公众号def quzao(content): if type(content) == str: i = re.sub('植物生物技术Pbj交流群', '', str(content)) i = re.sub('为了能更有效地帮助广大的科研工作者获取相关信息.*', '', str(i)) return i else: return ' '# 爬虫主函数def spider(): # 加载cookies load_cookies() # 访问官网主页 url = 'https://mp.weixin.qq.com' res = s.get(url = url, headers = headers, cookies = cookies, verify = False) if res.status_code == 200: # 由于加载了cookies,相当于已经登陆了,系统作了重定义,response的url中含有我们需要的token print(res.url) # 获得token token = re.findall(r'.*?token=(\\d+)', res.url) if token: token = token[0] else: # 没有token的话,说明cookies过时了,没有登陆成功,退出程序 print('登陆失败') return print('token', token) # 检索公众号 url = 'https://mp.weixin.qq.com/cgi-bin/searchbiz' data = { "action": "search_biz", "begin": "0", "count": "5", "query": key_word, "token": token, "lang": "zh_CN", "f": "json", "ajax": "1" } # 继续使用会话发起请求 res = s.get(url = url, params = data, cookies = cookies, headers = headers, verify = False) if res.status_code == 200: # 搜索结果的第一个提取它的fakeid fakeid = res.json()['list'][0]['fakeid'] print('微信公众号fakeid', fakeid) page_size = 5 # 默认是5天文章1页,这个参数似乎最大值只有5 page_count = 278 # 公众号文章总页数(自己手动调整,爬取到第几页) cur_page = 1 # 爬取页数(从第几页开始爬取) l = 1 # excel计数用 while cur_page <= page_count: url = 'https://mp.weixin.qq.com/cgi-bin/appmsg' data = { "action": "list_ex", "begin": str(page_size * (cur_page - 1)), "count": str(page_size), "fakeid": fakeid, "type": "9", "query": "", "token": token, "lang": "zh_CN", "f": "json", "ajax": "1" } time.sleep(random.randint(1, 5)) #继续会话发起请求 res = s.get(url = url, params = data, cookies = cookies, headers = headers, verify =False) if res.status_code == 200: print('开始爬取页数:', cur_page) # 文章列表位于app_msg_list字段中 app_msg_list = res.json()['app_msg_list'] for item in app_msg_list: # 通过更新时间戳获得文章的发布日期 item['post_date'] = time.strftime("%Y-%m-%d", time.localtime(int(item['update_time']))) if int(item['post_date'].split('-')[0])<last_date: continue # 以下标题分离只适合该公众号 if item['title'].find("|") != -1: # 有竖线分离副标题 title = item['title'].split("|")[1].strip() cat = item['title'].split("|")[0].strip() elif item['title'].find("|") != -1: title = item['title'].split("|")[1].strip() # 分离中文竖线 cat = item['title'].split("|")[0].strip() elif item['title'].find("│") != -1: title = item['title'].split("│")[1].strip() # 分离另一种很神奇的竖线 cat = item['title'].split("│")[0].strip() else: title = (item['title']) cat = 'N/A' title_list.append(title) date_list.append(item['post_date']) link_list.append(item['link']) author_list.append(key_word) cat_list.append(cat) response = requests.get(url = item['link'], headers = headers) print("正在解析网页" + str(item['link']) + '......') time.sleep(random.randint(1, 5)) # 爬一个,休息1-5秒 tree_content = etree.HTML(response.text) # 获取爬到的动态页面源码 try: # 解析xpath,去噪 content = tree_content.xpath(xpath_string) content = re.sub(r'\\s+', '', ''.join(content)) # 获取到的文章内容(去空格) content = quzao(content) except: content = '' # 没有内容的可能是内容违规已撤销 content_list.append(content) print('解析文章"'+title+'"成功!') try: # 以下逐行写入,备份数据用,防止反爬造成数据丢失 sheet.write(l , 0, title) sheet.write(l , 1, key_word) sheet.write(l , 2, content) sheet.write(l , 3, cat) sheet.write(l , 4, item['link']) sheet.write(l , 5, item['post_date']) savepath = './微信公众号_' + key_word + '_.xls' l += 1 if l % 20 == 0: # 每20行保存一次(适当调大一点,以免保存失败) book.save(savepath) print("数据备份成功!已保存" + str(l) + "条!") except: continue # 当前页面数+1 cur_page += 1 print('over!开始保存') # 中途没有反爬的话,一次写入所有爬取数据 data = {'title': title_list, 'author': author_list, 'content': content_list,'category': cat_list,"link":link_list,"date":date_list} df = DataFrame(data) df.to_excel('./微信公众号_' + key_word + '_.xlsx') print('保存成功!')spider() 代码只有cookie需要登录微信公众号后台手动获取,复制粘贴进去;page_count由刚才查文章的界面往下拉,找到一共有多少页,其他参数都不用修改。 为了防止半路被反爬,引入了xlwt库,作用是创建工作表,逐行写入保存爬到的临时数据,不然有可能爬到一半被检测到,最后所有数据都不会保存(别问我为什么知道)!最后一步是写入所有数据,名称和临时数据不一样,也是多一步保险措施。爬取过程显示的数据如下: 如果中途被反爬机制检测到,换一个公众号cookie,然后从中断的cur_page处继续,excel另存。 通过以上代码,实现对公众号“植物生物技术Pbj”8447篇推送(从创建的第一天2019年3月1日至2022年12月18日)爬取: 上面的代码是根据“植物生物技术Pbj”这个公众号排版所特制的,一定要注意根据具体公众号决定制作怎么样的去噪函数,总页数其实也可以根据app_msg_count/page_size值写一个函数自动计算出来,但是如果中途被反爬还是要手动改page值,这里就不多此一举了。还有,如果爬取的每页推送天数(也就是第二个data字典中的count值)可以突破5的话,就可以再写一个循环尽量一次拿到多的文章url,这样检测的机率就会大大降低(我每次被检测都是抓取app_msg_list的时候,而不是抓取文章内容的时候)。 经过半天的测试,有一点以下的经验之谈 微信公众号反爬机制可能是检测你翻页的次数,一天翻页的次数在100-200次间比较保险,也就是一次爬500天-1000天内的推送数据 每次抓取数据sleep1-5秒比较靠谱,1-3秒也容易在爬100条左右的时候被抓…… 有一个思路是通过保存所有文章url,再进行每个文章内容抓取,但是获取文章列表的data字典中count值最大只能是5,导致我们需要频繁翻页,这个地方如何突破是一个问题。 sheet.write方法有的时候会失效,在某一次打开excel之后可能没来得及写入数据就被保存,导致后续无法继续保存临时数据。解决方法之一是保存间隔大一点,这里我设置了每写入20行保存一次。 做好爬取数据的双保险!我这里做了临时数据保存,不要抱侥幸心,不然爬半天数据容易全部木大!","categories":[{"name":"网络相关","slug":"网络相关","permalink":"http://www.shelven.com/categories/%E7%BD%91%E7%BB%9C%E7%9B%B8%E5%85%B3/"}],"tags":[{"name":"python","slug":"python","permalink":"http://www.shelven.com/tags/python/"},{"name":"爬虫","slug":"爬虫","permalink":"http://www.shelven.com/tags/%E7%88%AC%E8%99%AB/"}]},{"title":"应用requests、Xpath和selenium编写爬虫脚本","slug":"应用requests、Xpath和selenium编写爬虫脚本","date":"2022-12-13T16:42:48.000Z","updated":"2022-12-13T16:47:45.000Z","comments":true,"path":"2022/12/14/a.html","link":"","permalink":"http://www.shelven.com/2022/12/14/a.html","excerpt":"这篇博客承接前面的HTTP基本原理,对requests、Xpath和selenium三个库/工具做个简单介绍,并且用三个爬虫实例由浅到深理解爬虫的构思和实现过程,最后是用selenium+chromedriver模拟浏览器,实现对微信公众号文章的爬取。","text":"这篇博客承接前面的HTTP基本原理,对requests、Xpath和selenium三个库/工具做个简单介绍,并且用三个爬虫实例由浅到深理解爬虫的构思和实现过程,最后是用selenium+chromedriver模拟浏览器,实现对微信公众号文章的爬取。 1. requestsrequests是python最常见的HTTP客户端库,可以调用requests模块的API伪装成浏览器对网站发起请求。 前面一篇爬虫博客介绍了requests的六种方法,这里不多赘述,主要回顾下发送请求和获得响应的过程。 requests库有两个重要的对象,Request和Response,Request对象对应的是请求,向目标网址发送一个请求访问服务。而Response对象,是包含了爬虫返回的内容。 Request对象完整的发起get和post请求方式: 1234567requests.get(url, params=None, **kwargs)requests.post(url, data=None, **kwargs)# url:想要获取的网页链接# params:显示在url中的参数,字典形式# data:不显示在url中,通过提交表单的方式提交参数,也是字典形式# **kwargs:控制访问的参数,字典形式 当服务器正常响应时,返回状态码200,这个时候就可以用Response对象的属性来获取网页信息。 Response对象属性: .status_code:HTTP响应状态码,200表示成功,其他状态码详见上篇博客 .text:HTTP响应体内容的字符串格式 .content:HTTP响应体内容的二进制格式 .encoding:从HTTP header中猜测的响应内容编码方式 .apparent_encoding:从内容中分析出的响应内容编码方式(备选编码方式) 这里需要注意,如果我们获得的响应内容是图片视频和音频的话,需要用二进制格式进行储存。 有了以上基础知识,就可以用request写一个爬虫项目了,我们现在目的是爬取豆瓣电影古装排行榜前20的电影图片。 进入豆瓣电影排行榜网页,按F12进入浏览器开发者工具,点击网页页面分类中的“古装”,对网页数据进行抓包。当鼠标滚轮往下滚动的时候我们可以发现,每次滚动更新,有一个名字很长的数据包会不断更新,还伴随着一大堆jpg图片的出现,很明显这个数据包是我们要抓取的对象。 点击表头选项的响应头,我们看到返回的数据是json格式,编码方式是utf-8。请求url栏中问号之前的部分是我们要的url,参数可以设置一个字典传入。 在负载部分,多次抓包以后可以看到前三个参数是一直不变的,猜测这几个参数可能是和电影类型和页面布局相关,这个可以不用管。翻页刷新后总是固定显示20个电影,因此limit和数据包内抓取的电影数相关,start和这个数据库中起始的电影编号相关。 再看看响应的json数据中,有一个“cover_url”的键对应值是电影的图片地址,至此思路就很明确了。 1234567891011121314151617181920212223242526import requestsurl = 'https://movie.douban.com/j/chart/top_list'# 传入的url参数设置param = { 'type': '30', # 影片类型代号 'interval_id': '100:90', 'action':'', 'start': '0', # 从第一个影片开始 'limit': '10', # 需要抓取的影片数}# 伪装浏览器headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.46'}# 抓取上面定义范围内所有影片信息response = requests.get(url = url, params = param, headers = headers)# json字符串反序列化为list类型ls = response.json()# 解析电影图片地址,并抓取和保存图片for p in ls: with open('./' + str(p['title']) + '.jpg','wb') as img: response1 = requests.get(url = p["cover_url"], headers = headers) img.write(response1.content) print('over!!!') .json()这个requests库自带的函数还是挺有意思的,在这个例子中是将返回的字符串JSON数据反序列化为list数据,list中嵌套了字典数据,每个电影的信息都储存在字典中。因此这里也可以用.json()['cover_url']直接对图片网址进行抓取,注意下这里第二次抓取的是图片,返回的是二进制数据,所以用content而不是text。 2. Xpath前面例子抓包返回的是JSON字符串,所以可以直接提取我们要的信息。如果返回的是HTML源代码,就可以用正则或者Xpath来解析我们想要的数据。 Xpath是一种解析XML文档信息的工具,我们可以通过lxml库(XML和HTML的解析库)中导入etree模块,实例化etree对象,使用xpath函数结合xpath表达式进行标签定位和指定数据的获取。 Xpath常用规则: 表达式 描述 nodename 选取此节点的所有子节点 / 从当前节点选取直接子节点 // 从当前节点选取所有子孙节点 . 选取当前节点 .. 选取当前节点的父节点 @ 选取属性 Xpath常用表达式: 123456789101112属性定位 //div[@class="slist"] # 找到所有class属性值为slist的div标签层级定位 //div[@class="slist"]/../li[2] # 找到class属性值为slist的div的父标签下的第二个直系子标签li多属性解析 //div[@class="slist" and @name="Id"] # 找到class属性为slist以及name属性为Id的div标签模糊匹配 //div[contains(@class, "slist")] # 找到class属性中包含slist值得div标签文本获取 //li[@class="item"]//text() # 取出class属性值为item的所有li标签下的所有标签文本(包括子标签) 获取属性 //li/a/@href # 取出所有li标签下a标签下的href属性值 etree对象实例化: 1234567本地文件(解析保存在本地的HTML文件):tree = etree.parse(文件名)tree.xpath("xpath表达式")网络数据(实例化一个html类):tree = etree.HTML(网页内容字符串)tree.xpath("xpath表达式") 注意下xpath解析出来的数据是以列表形式存储的,接下来示范一下requests结合Xpath写一个爬虫程序,目的是抓取彼岸图网的4k动漫封面图。 进入页面以后同样F12审查元素,点击不同页抓取返回的数据包,发现翻到第x页能抓到index_x.html,但是第一页没有下划线和其他页稍有不同,为了方便起见就从第二页开始抓。 仔细观察可以发现,所有封面图都在属性值为slist的div标签下的ul标签下的li标签中(这么说着好绕= =),前面http原理的博客说过,这样的标签可以看出是是无序列表,我们要找的封面图片地址可以通过img标签的src属性获得,图片名称可以通过alt属性获得,因此可以写如下的爬虫代码: 1234567891011121314151617181920212223242526272829303132import requestsfrom lxml import etreeimport os# 举个例子,这里抓取的是第二页数据for i in range(2,3): url = 'http://pic.netbian.com/4kdongman/index_'+str(i)+'.html' headers = { 'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.3' } response = requests.get(url = url, headers = headers) # 获取网页内容字符串 page_text = response.text # 实例化etree对象,获取所有图片li标签信息(列表格式) tree = etree.HTML(page_text) li_list = tree.xpath('//div[@class="slist"]/ul/li') # 创建文件夹 if not os.path.exists('./pic'): os.mkdir('./pic') # 解析li标签下孙子标签img的src属性和alt属性 for li in li_list: img_src = 'http://pic.netbian.com' + li.xpath('./a/img/@src')[0] img_name = li.xpath('./a/img/@alt')[0] + '.jpg' # 这里需要处理中文乱码问题,重新编码和解码,gbk是中文最常用的 img_name = img_name.encode('iso-8859-1').decode('gbk') img_data = requests.get(url = img_src, headers = headers).content img_path = 'pic/' + img_name with open(img_path,'wb') as fp: fp.write(img_data) print(img_name,'下载成功!!!') 当然,上面爬虫抓取的只是封面图片,并不是高清图片,因为高清图片是需要登录账号花钱下载的….我们只能合法地从我们能在浏览器中看到的图片爬取信息。而且如果你在短时间内发起大量请求的话,ip是很有可能被封的,以后再讲一些预防反爬的措施。 3. seleniumselenium是一个自动化测试工具,本质是通过驱动浏览器,完全模拟浏览器中的操作,比如拖动、点击下拉等等。为什么要模仿浏览器中的操作呢?因为很多网站是动态加载的,requests这一类的模块无法直接执行JavaScript代码,这个时候就可以通过测试工具selenium模仿人在浏览器中的操作,从而获得网页渲染之后的结果。 selenium官方网站:Selenium (很暖心地有中文文档)以最新的selenium指南为基础,简单介绍一下其用法。 强调两点: selenium在4.3版本做了代码重构,很多方法被改写,最重要的是find_element方法的改写,具体点击这里查看官方消息。本篇博客所有写法将按照最新的语法规则 一定要注意自己的浏览器与驱动版本是否匹配,本篇博客以chrome浏览器为例,chrome浏览器驱动程序官方下载地址请点击http://chromedriver.storage.googleapis.com/index.html 简单地以淘宝首页作为例子: 12345678910111213141516171819202122232425262728from selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.chrome.service import Servicefrom time import sleep# 启动前先将驱动程序放在当前页面bro = Service(executable_path = './chromedriver.exe')# 启动谷歌浏览器driver = webdriver.Chrome(service = bro)# 进入淘宝网页页面driver.get('https://www.taobao.com/')# 标签定位,输入栏id属性值为'q',id属性是整个html中唯一的,不会重复search_input = driver.find_element(By.ID, 'q')# 节点交互,向输入栏中输入数据'Iphone'search_input.send_keys('Iphone')# 标签定位,找到搜索按钮(可以用css选择器、id值或者Xpath等定位,这里用css选择器)btn = driver.find_element(By.CSS_SELECTOR, '.btn-search')# 节点交互,点击搜索按钮btn.click()driver.get('https://www.baidu.com')sleep(2)# 回退driver.back()sleep(2)# 前进driver.forward()sleep(2)# 退出浏览器driver.quit() 简单来说,流程可以分为 创建驱动实例开启会话 driver = webdriver.Chrome() 在浏览器中执行操作 driver.get("https://www.baidu.com") 请求浏览器信息 title = driver.title 建立等待策略(隐式或显示) driver.implicitly_wait(0.5)或者用sleep(1) 定位标签 text_box = driver.find_element(by=By.NAME, value="my-text") 节点交互 text_box.send_keys("Selenium") 获取信息 value = message.text 结束会话,关闭浏览器 driver.quit() 浏览器创建selenium支持的浏览器有Chrome、Firefox、Edge、Internet Explorer和Safari。参考支持的浏览器列表 | Selenium 元素定位webdriver提供了8种内置的定位方法: 12345678910from selenium.webdriver.common.by import Byfind_element(By.ID, 'value') find_element(By.NAME, 'value')find_element(By.CLASS_NAME, 'value')find_element(By.TAG_NAME, 'value')find_element(By.LINK_TEXT, 'value')find_element(By.PARTIAL_LINK_TEXT, 'value')find_element(By,XPATH, 'value')find_element(By.CSS_SELECTOR, 'value') 节点交互常见的节点交互有以下内容: 123456789101112driver.get("https://www.baidu.com") # 打开网站driver.back() # 浏览器后退driver.forward() # 浏览器前进driver.refresh() # 浏览器刷新driver.add_cookie({"name": "key", "value": "value"}) # 当前浏览器添加cookiedriver.find_element(By.LINK_TEXT, "new window").click() # 新窗口中打开链接driver.find_element(By.ID, 'value').send_keys('value') # 定位并发送内容driver.switch_to.new_window('tab') # 打开新标签页并切换到新标签页driver.switch_to.new_window('window') # 打开新窗口并切换到新窗口driver.switch_to.window(original_window) # 切回之前的标签页或窗口driver.close() # 关闭标签页或窗口driver.quit() # 关闭浏览器 动作链上面说的交互是对页面中存在的标签或者说是元素进行交互,而对于没有特定对象的,比对鼠标的双击、拖拽,鼠标滚轮的操作,键盘的输入等,这些操作就需要动作链来执行。该部分内容官网有详细介绍,这里就举个例子了解一下怎么用的就行。 12345678910111213141516171819202122232425import timefrom selenium.webdriver.common.by import Byfrom selenium import webdriverfrom time import sleepfrom selenium.webdriver import ActionChainsbro = webdriver.Chrome(executable_path = './chromedriver')bro.get('https://www.runoob.com/try/try.php?filename=jqueryui-api-droppable')# 如果定位的标签是存在于iframe标签之中的则必须通过如下操作在进行标签定位bro.switch_to.frame('iframeResult') # 切换浏览器标签定位的作用域div = bro.find_element(By.ID, 'draggable')# 动作链action = ActionChains(bro)# 点击长按指定的标签action.click_and_hold(div)for i in range(10): action.move_by_offset(5,0) # move_by_offset(x,y):x水平方向 y竖直方向 action.perform() # 执行动作链操作time.sleep(3)#释放动作链action.release()bro.quit() 微信公众号爬虫范例123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596import randomimport refrom pandas import DataFrameimport osimport requestsimport timefrom selenium import webdriverfrom selenium.webdriver.support import expected_conditions as ECfrom selenium.webdriver.support.wait import WebDriverWaitfrom selenium.webdriver.common.by import Byfrom time import sleepfrom selenium.webdriver import ChromeOptionsfrom lxml import etree# moder可以为author或者article,前者为按公众号搜索,后者为按文章关键字搜索modern='author'# 搜索的关键字,如果modern = author,输入公众号名字,否则输入文章关键字keyword = '冷兔'# 爬取多少页,建议先手动搜索最大页码数,page大于最大页码将会报错page = 2headers = { 'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36' }# 实现规避检测option = ChromeOptions()option.add_experimental_option('excludeSwitches', ['enable-automation'])bro = webdriver.Chrome(executable_path = './chromedriver', options=option)bro.get("https://www.sogou.com/index.php")wait = WebDriverWait(bro, 2)search_input = bro.find_element(By.ID, 'query')search_input.send_keys(keyword)# 点击搜索按钮btn = bro.find_element(By.ID, 'stb')btn.click()# 点击微信登陆time.sleep(2)btn = bro.find_element(By.XPATH, '//*[@id="loginBtn"]')btn.click()# 用微信扫码,只有十秒time.sleep(2)btn = bro.find_element(By.XPATH, '/html/body/div[3]/div[3]/div[2]/div[4]/div/a[2]')btn.click()time.sleep(10)button = bro.find_element(By.ID, 'sogou_weixin')button.click()article_button = bro.find_element(By.XPATH, '//*[@id="scroll-header"]/form/div/input[1]')article_button.click()# 最后需要爬取的文章url都在这里url_list=[]for i in range(page): page_text = bro.page_source # 解析 tree = etree.HTML(page_text) author=tree.xpath('/html/body/div[2]/div[1]/div[3]/ul/li/div[2]/div/a/text()') print(author) url_page_list=tree.xpath('/html/body/div[2]/div[1]/div[3]/ul/li/div[2]/h3/a/@href') # 下面这个循环判断按author爬取还是按照aiticle爬取 for j in range(len(author)): if author[j]==keyword: url_list.append(url_page_list[j]) elif modern=='article': url_list.append(url_page_list[j]) else: continue if i != page - 1: next_button = bro.find_element(By.ID, 'sogou_next') next_button.click() time.sleep(2)# 拼接地址url_list=['https://weixin.sogou.com/'+i for i in url_list]title_list=[]content_list=[]time_list=[]author_list=[]for url in url_list: response = bro.get(url=url) time.sleep(random.randint(1, 3)) # 爬一个,休息1-3秒,怕被抓 page_text = bro.page_source tree_content = etree.HTML(page_text) # 获取爬到的动态页面源码 title = tree_content.xpath('/html/body/div[1]/div[2]/div[1]/div/div[1]/h1/text()') title = re.sub(r'\\s', '', ''.join(title)) # 获取到的文章题目 content = tree_content.xpath('/html/body/div[1]/div[2]/div[1]/div/div[1]/div[3]//text()') content = re.sub(r'\\s*', '', ''.join(content)) # 获取到的文章内容 time1=tree_content.xpath('//*[@id="publish_time"]/text()')[0] author=tree_content.xpath('//*[@id="js_name"]/text()')[0] author=re.sub(r'\\s', '', ''.join(author)) title_list.append(title) content_list.append(content) time_list.append(time1) author_list.append(author)# 写入xlsx文件中data = {'title': title_list, 'time':time_list,'author':author_list,'content': content_list}df = DataFrame(data)df.to_excel('./微信公众号_'+keyword+'.xlsx') 这里规避检测识别是设置Chromedriver的启动参数,在启动Chromedriver之前,为Chrome开启实验性功能参数excludeSwitches,它的值为[‘enable-automation’]。这个参数有什么作用呢? 我们正常运行selenium时,最上方是有提示”Chrome正受到自动测试软件的控制“的,这个参数设置就是禁用浏览器的提示。如果我们用selenium模拟chrome浏览器访问网站,网站可以通过检查window.navigator.webdriver返回值判断我们是用正常的浏览器访问还是用selenium模拟浏览器发起的访问。 如果返回值为undefined,说明是正常浏览器;如果返回true说明用selenium模拟的浏览器。 为Chrome开启实验性功能参数excludeSwitches后,和正常浏览器一样返回的是undefined。 当然,反爬手段不仅仅是这一个,这个以后的有空再细说。上面的爬虫代码参考了刘丹老师,不是最终版本,还可以对输出内容和样式继续做优化。简单看一下实现的结果:","categories":[{"name":"网络相关","slug":"网络相关","permalink":"http://www.shelven.com/categories/%E7%BD%91%E7%BB%9C%E7%9B%B8%E5%85%B3/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"http://www.shelven.com/tags/%E7%88%AC%E8%99%AB/"},{"name":"requests","slug":"requests","permalink":"http://www.shelven.com/tags/requests/"},{"name":"Xpath","slug":"Xpath","permalink":"http://www.shelven.com/tags/Xpath/"},{"name":"selenium","slug":"selenium","permalink":"http://www.shelven.com/tags/selenium/"}]},{"title":"HTTP基本原理","slug":"HTTP基本原理","date":"2022-12-10T15:10:45.000Z","updated":"2022-12-10T16:00:15.000Z","comments":true,"path":"2022/12/10/a.html","link":"","permalink":"http://www.shelven.com/2022/12/10/a.html","excerpt":"以前写过一篇博客如何爬取微博热搜的前50条,当时是从代码出发理解爬虫实现的过程。这篇博客主要讲一下HTTP的基本知识,知道从浏览器中输入网址到我们获取网页内容的过程中发生了什么,有助于进一步了解爬虫的基本原理。","text":"以前写过一篇博客如何爬取微博热搜的前50条,当时是从代码出发理解爬虫实现的过程。这篇博客主要讲一下HTTP的基本知识,知道从浏览器中输入网址到我们获取网页内容的过程中发生了什么,有助于进一步了解爬虫的基本原理。 1. URI、URL和URN先放上这三个名词的定义: URI:Uniform Resource Identifier 统一资源标志符 URL:Uniform Resource Locator 统一资源定位符 URN:Uniform Resource Name 统一资源名称 URI是一个抽象定义,只要能定位到一个资源,都叫做URI。 URL和URN都是URI的子集,简单来说,URL用地址定位资源,URN用名称定位资源。只是后来URN在互联网中使用非常少,导致现在几乎所有的URI都是URL,因此我们可以将一般网页链接称之为URI或者URL(后者用的最多)。 那么URL或者URI具体指什么呢? 举个例子,本站图标地址https://www.shelven.com/tuchuang/bitbug_favicon.ico,通过这个地址可以访问到一只32*32像素大小的可爱小猫,通过这个地址URL/URI指定了它的访问方式,包括了访问协议https,访问路径和资源名称bitbug_favicon.ico。这个访问资源可以是一个图片,一个CSS文档,一个HTML页面等等。 以HTTPS协议访问web服务器为例,拆解一下完整的URL结构: https://user:password@www.shelven.com:443/tuchuang/bitbug_favicon.ico 协议:URL开头部分必须是协议类型,常见的https、http、ftp和mailto,指明浏览器应当使用的访问方法,用//做分隔符 用户名/密码:user:password这部分可以省略 域名:我这里域名是www.shelven.com,我们在发送请求前会向DNS服务器解析这个域名的ip地址,域名只是方便我们人类记忆的,计算机访问的最终都是ip地址。当然,如果你能记得住ip地址也可以直接输入。 端口:用来区分不同网络服务(web服务、ftp服务等),和域名之间用冒号:分隔,端口不是URL必须的部分,http默认端口80,https默认端口443,ftp默认端口21。 文件路径/文件名:从域名第一个/到最后一个/之间是虚拟目录;从域名最后一个/到?部分是文件名,没有?则是到#为止,都没有则是从最后一个/一直到结束都是文件名部分。文件名是可以缺省的。 2. 超文本超文本(Hyper Text, HT)是用超链接的方法,将不同空间文字信息组织在一起的网状文本。 举个例子,浏览器中看到的网页就是超文本解析而来的,网页本身就是一个文本文件,而超文本指这种文件既可以包含文本信息,又可以包含图片、视频、链接等非文字信息。 网页的源代码是一系列HTML(Hyper Text Markup Language, 超文本标记语言)代码,里面包含了一系列标签(尖括号<>包围的关键词,一般成对出现)和属性值。在浏览器中打开任意一个页面,按F12就可以打开浏览器的开发者工具,选择元素(Elements)选项卡就可以看到当前网页的源代码,而这些源代码都是超文本。 红框框住的左上角箭头,点击以后可以在页面中用鼠标悬停选中元素,右边对应的源代码部分会高亮,方便我们进行元素审查。 这里顺便记录下HTML常用的标签和属性: 标签名 用法 基本结构标签 HTML标签(根标签) <html></html> 文档头标签 <head></head> 文档标题标签(网页标题) <title></title> 文档主体标签(页面内容) <body></body> 列表标签 (这里因为渲染问题&emsp无法显示空格…知道HTML有缩进的意思就行) 无序列表 <ul type=”disc/circle/square”>&emsp;<li>条目内容</li></ul> 有序列表 <ol type=”1/a/A/i/I”>&emsp;<li>条目内容</li></ol> 定义列表 <dl>&emsp;<dt>列表标题标签</dt>&emsp;<dd>具体列表项</dd></dl> 表格标签 表格标签(tr为行) <table>&emsp;<tr>&emsp;&emsp;<td>单元格内容</td>&emsp;</tr></table> 常用标签 标题标签(h1-h6) <h1>一级标题</h1> 段落标签 <p>这里是内容</p> 字体标签 <font size=”10” color=”black” face=”微软雅黑”>你好</font> 换行标签 <br/> 水平线标签 <hr size=”10” color=”red” width=”50%” align=”left”/> 盒子标签div <div>div标签内容独占一行</div> 盒子标签span <span>span标签内容一行可以多个</span> 图片标签 <img src=”地址” width=’”宽度” height=”高度”></img> 超链接标签 <a href=”跳转网址” target=”窗口弹出方式”></a> 注释标签 <!– 注释内容 –> 还有表单标签<form></form>等等,太多了这里就不一一详细说了,如果以后有必要再出一个详细的HTML笔记,现在只要看到这些标签心里有个数就行,真正要做前端再去详细探究。 3. 协议前面说URL的开头必须指明协议类型,常用的是ftp(文件传输协议)、http(超文本传输协议)、https(http的安全版)、mailto(电子邮件协议)和smb(通信协议)。不需要对所有协议了如指掌,前三中协议是我们日常用的最多的,http和https是我们访问网站web服务所必须的,爬虫也可以通过这两种协议伪装成浏览器访问,从而抓取我们需要的页面。 HTTP(Hyper Text Transfer Protocol, 超文本传输协议)就是一个简单的请求-响应协议,运行在TCP之上,指定客户端发送什么样的消息以及得到什么响应,服务器端实现程序有httpd(本站就是用的这个)和nginx。 HTTPs(Hyper Text Transfer Protocol over Secure Socket Layer)以安全为目标的HTTP通道,说白了就是安全版HTTP,在HTTP下加入SSL层,传输内容经过SSL加密。 本站建站之初,我当时绕了一大圈才签下来SSL证书……HTTPS的安全基础是SSL,主要作用是建立一个信息安全通道,保证数据传输的安全;确认网站的真实性,使用https的网站可以通过浏览器地址栏的锁头标志,查看网站认证的真实信息。 有些网站使用了HTTPs协议但还是会被浏览器提示不安全,那有可能是证书过期了,或者颁发CA证书的机构不是被信任的,这样就会提示”您的连接不是私密连接“。而要用爬虫爬取这种页面的话,需要设置忽略证书,否则会提示SSL证书连接错误。 4. HTTP请求过程我们在浏览器中输入一个 URL,回车之后便会在浏览器中观察到页面内容。这个过程是浏览器向网站所在的服务器发送了一个请求,网站服务器接收到这个请求后进行处理和解析,然后返回对应的响应,接着传回给浏览器。响应里包含了页面的源代码等内容,浏览器再对其进行解析,将网页呈现出来。 以本站作为演示,打开浏览器,按下F12进入开发者工具,点击网络(Network)选项;搜索框输入https://www.shelven.com,回车。观察整个过程中发生了怎样的网络请求。 看下第一个网络请求www.shelven.com,各列的含义如下: 名称name:请求的名称,返回的每一条都是对应的数据包 状态status:响应的状态码,通过状态码判断发送请求之后是否得到正常的响应 类型type:请求的文档类型,这里是返回document,内容就是一些html代码 发起程序initiator:请求源,标记是哪个对象或者进程发起的请求 大小size:从服务器下载的文件和请求资源的大小 时间time:发起请求到获取响应的总时间 时间线waterfall:网络请求的可视化瀑布流 点击具体条目可以看到更详细的信息。 主要看三个部分,常规(general)、响应头(Response Headers)和请求头(Request Headers)。常规部分是总的数据包概括。请求头带有请求信息,例如Cookies、user-agent等信息,服务器会根据请求头内的信息判断请求是否合法,进而作出对应的响应。响应头就是响应的一部分,包含了服务器的类型、文档类型、日期等信息,浏览器接受到响应后,会解析响应内容,进而呈现网页内容。 5. 请求请求指的是从客户端到服务器端的请求消息,发给服务器的请求称为请求报文,可以分为请求行(request line),请求头(request header)和请求体(request body)。 5.1 请求行请求行中包括了请求方法,请求协议和版本。 以百度首页为例: 小框框起来的地方为请求行,可以看到百度首页的请求方法为get,请求协议为HTTP版本1.1 常见的请求方法有两种:GET和POST GET 请求中的参数包含在URL里面,数据可以在URL中看到,而POST请求的URL不会包含这些数据,数据是通过表单形式传输的,会包含在请求体中。 GET 请求提交的数据最多只有1024字节,而POST方式没有限制。 因为GET请求方式不涉及和数据库的交换,所以我们浏览网页用的都是GET请求;如果要在一个网站登录,就需要提交用户名和密码的表单,这个时候用的就是POST请求。还有一个重要的因素,GET方式请求的数据是在URL中完全暴露的,所以也不会用GET方式发送请求,不然容易造成密码泄露。 其他请求方法在前面一篇爬虫博客有提到,这里列个表格: 方法 描述 GET 请求页面,并返回页面内容 POST 大多用于提交表单或上传文件,数据包含在请求体中 HEAD 类似于 GET 请求,只不过返回的响应中没有具体的内容,用于获取报头 PUT 从客户端向服务器传送的数据取代指定文档中的内容 DELETE 请求服务器删除指定的页面 CONNECT 把服务器当作跳板,让服务器代替客户端访问其他网页 OPTIONS 允许客户端查看服务器的性能 TRACE 回显服务器收到的请求,主要用于测试或诊断 表格参考:HTTP 请求方法 | 菜鸟教程 (runoob.com) 5.2 请求头请求头是用来说明服务器使用的附加信息的,上面那个百度首页例子的大框框住部分就是请求头的信息。 同样列个表格记录下常用的头信息。 头信息 描述 Accept 请求报头域,用于指定客户端可接受哪些类型的信息 Accept-Language 指定客户端可接受的语言类型 Accept-Encoding 指定客户端可接受的内容编码 Host 指定请求资源的主机IP和端口号 Cookie 而存储在用户本地的数据,主要功能是维持当前访问会话 Referer 标识请求是从哪个页面发过来的,服务器可以拿来做来源统计、防盗链处理 User-Agent 服务器识别客户使用的操作系统及版本、浏览器及版本等信息,爬虫伪装浏览器必备 Content-Type 请求的数据类型信息HTTP Content-type 对照表 (oschina.net) 5.3 请求体请求体承载POST请求中的表单数据,GET请求的请求体为空。 这里以登录github捕获的请求体为例: 登录的时候填写的用户名和密码信息,提交的时候就会以表单形式提交给服务器,这个时候可以看到请求头中的Cotent-Type为application/x-www-form-urlencoded,表示以表单数据的形式提交给服务器。可以设置不同的Cotent-Type,以不同的方式提交数据,如果在做爬虫的时候要构造POST请求,需要注意一下使用正确的Cotent-Type(类型/子类型),不然可能会提交后无法正常响应。 Cotent-Type 数据提交的方式 application/x-www-form-urlencoded 表单数据 multipart/form-data 表单文件上传 application/json 序列化JSON数据 text/xml XML数据 application/pdf pdf格式 application/octet-stream 二进制流数据(如常见的文件下载) 6. 响应和请求类似的,服务器进行HTTP响应也是分为三个部分:响应状态行,响应头和响应体 6.1 响应状态行回到之前百度首页的例子,我们点开百度首页审查元素,这次点开响应那一栏查看源。 小框框起来的地方是响应状态行,我们可以看到响应的协议版本是HTTP/1.1,响应状态码是200,说明返回正常。 响应状态码其实并不陌生,顾名思义表示服务器的响应状态。200说明服务器正常响应返回正常数据,经常能看到404报错,代表的是页面未找到,403表示服务器拒绝执行请求,503代表服务器不可用,301代表网页被永久转移到其他URL。 因为状态码非常多,这里就记录一下状态码的分类,详细状态码列表可以参考HTTP 状态码 | 菜鸟教程 (runoob.com) 分类 分类描述 100-199 信息响应,服务器收到请求,需要请求者继续执行操作 200-299 成功响应,操作被成功接收并处理 300-399 重定向,需要进一步的操作以完成请求 400-499 客户端错误,请求包含语法错误或无法完成请求 500-599 服务器错误,服务器在处理请求的过程中发生了错误 6.2 响应头上面例子中红色大框框住的部分就是响应头,包含服务器对请求的应答信息。 响应头主要有如下的信息: 头信息 描述 Date 标识响应产生的时间 Last-Modified 指定资源的最后修改时间 Content-Encoding 指定响应内容的编码 Server 服务器的信息,比如名称、版本号 Content-Type 返回的数据类型信息 Set-Cookie 设置 Cookies,下次请求会携带这个cookies Expires 指定响应的过期时间 6.3 响应体响应体就是响应的内容,请求网页的时候响应体就是对应的HTML源代码,请求一张图片,响应体就是返回的二进制数据。爬虫就是通过请求到网页后,解析响应体中的内容(有的时候是HTML代码,有的时候是JSON数据等等,这两者比较常见),然后从中提取我们要的信息。 在edge浏览器中,进入开发者工具,点击network选项,选中需要解析的项目名称,点击响应就可以看到返回的响应体数据了。 以上暂时总结这么多HTTP的基础,参考了相当多的内容,后面做爬虫练习自然会用到。","categories":[{"name":"网络相关","slug":"网络相关","permalink":"http://www.shelven.com/categories/%E7%BD%91%E7%BB%9C%E7%9B%B8%E5%85%B3/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"http://www.shelven.com/tags/%E7%88%AC%E8%99%AB/"},{"name":"HTTP","slug":"HTTP","permalink":"http://www.shelven.com/tags/HTTP/"}]},{"title":"python自学笔记(7)——模块、包和库","slug":"python自学笔记(7)——模块、包和库","date":"2022-11-29T14:30:03.000Z","updated":"2022-12-03T15:51:32.000Z","comments":true,"path":"2022/11/29/b.html","link":"","permalink":"http://www.shelven.com/2022/11/29/b.html","excerpt":"前面介绍递归函数的时候用到了sys模块,介绍文件操作函数的时候用到了os模块,之前只是简单说了这两个模块下部分函数的用法,这里详细介绍一下对于模块、包和库的概念,以及一些常见的模块用法。","text":"前面介绍递归函数的时候用到了sys模块,介绍文件操作函数的时候用到了os模块,之前只是简单说了这两个模块下部分函数的用法,这里详细介绍一下对于模块、包和库的概念,以及一些常见的模块用法。 不需要记住每个模块下所有函数用法,但是平常看到python文件导入模块操作的时候,要大概知道这几个模块有什么作用。 1. 概念1.1 模块(module)函数可以理解为完成特定功能的一段程序,类是包含一组数据及操作这些数据或传递消息的函数的集合,而模块(module)是在函数和类的基础上,将一系列相关代码组织到一起的集合体。 在python中,扩展名为.py的源程序文件就是一个模块,这个和C语言的头文件以及JAVA的包是类似的。 python官方网站上可以查看当前标准库中的所有模块,点击这里。 1.2 包(package)为了方便调用将一些功能相近的模块组织在一起,或是将一个较为复杂的模块拆分为多个组成部分,可以将 .py 源程序文件放在同一个文件夹下,按照 Python 的规则进行管理,这样的文件夹和其中的文件就称为包(package)。 包的目录下需要创建__init__.py 模块,可以是一个空文件,可以写一些初始化代码,其作用就是告诉 Python 要将该目录当成包来处理,让python认为你这是一个包而不是单纯的一个目录(否则会显示找不到包)。有的博客说python3.3版本之后不需要空的__init__.py 模块来声明这是一个包了,但是我在vscode和jupyter运行python3.10的时候发现还是需要__init__.py 模块声明的,这里先存疑,我保留自己的观点。 2022.12.3更新:准确来说,从包里导入模块需要__init__.py 声明;直接导入同目录下的模块不需要(3.3版本以后) 简单来说,包就是有层次地文件目录结构,里面装着各种扩展名.py的python源程序文件,包中也可以含有包。 1.3 库库顾名思义则是功能相关联的包的集合。python的三大特色之一:强大的标准库,第三方库以及自定义模块。 2. 常用模块/库python的三大特色对应三种类型的模块,标准库的内置模块,第三方库开源模块和自定义的模块,这里简单记录一下常用的模块/库。 模块名称 介绍 内置模块 os 普遍的操作系统功能接口,包括前面介绍的文件操作函数 sys 提供了一系列有关Python运行环境的变量和函数,sys.path.append() random 生成随机数,random() 返回0<n<=1 time 各种提供日期、时间功能的类和函数,time.time() 时间戳 datetime 对time模块的一个高级封装 logging 日志打印到了标准输出中 re 可以直接调用来实现正则匹配,re.split() 分割字符串,格式化列表 pymysql 连接数据库,并实现简单的增删改查 threading 提供了更强大的多线程管理方案 json 用于字符串和数据类型间进行转换json subprocess 像linux一样创建运行子进程 shutil 对压缩包的处理、对文件和文件夹的高级处理,os的补充 tkinter Python的标准Tk GUI工具包的接口 第三方模块/库 Requsests python最有名的第三方HTTP客户端库 Scrapy 屏幕抓取和web抓取框架,编写爬虫用到(上面的也可以) Pillow 常用的图像处理库 Matplotlib 绘制二维数据图的库,使用方式对标matlab NumPy 提供大型矩阵计算公式,在很多领域都用到 Pandas 基于Numpy 和 Matplotlib,和上面两个组成数据分析三剑客 Django 开源的web开发框架 PyTorch 开源的深度学习框架,各种张量操作、梯度计算,方便构建各种动态神经网络 TensorFlow 也是机器学习库,张量的操作和运算,tensorboard可视化数据很强大 第三方库实在太多,这里只列举了我知道的比较常见的库;内置模块可以见1.1章节的官网链接,里面有所有内置模块的具体用法。接下来说说怎么导入模块和制作模块。 3. 导入包和模块3.1 导入模块制作模块要注意,自定义的模块名不能和系统内置的模块重名,否则被重名的系统模块无法被导入。 python中用关键字import引入某个模块,在调用模块中的函数时,需要以 模块名.函数名 的方式进行引用。自定义模块名中的函数是可以重名的,因为模块名不会相同(同一层目录下文件名不同),调用的时候可以进行区分,这很好理解。 3.2 导入包有的时候我们只需要包里的某个模块或者模块里的某个函数,而不需要包或者模块里的全部内容,这个时候我们可以用关键词 from 包名/模块名 import 模块名/函数名 来进行调用。 举个例子,在如下的文件结构中,main.py作为主程序入口,test文件夹相当于一个包,里面有4个.py后缀的模块,分别定义了四则运算的函数,__init__.py 是个空文件(暂时不做处理),声明test文件夹是个python包而不是普通的目录。 123456789101112131415# add.py文件内容——定义加法运算def add(a, b): return a + b# sub.py文件内容——定义减法运算def sub(a, b): return a - b# mul.py文件内容——定义乘法运算def mul(a, b): return a * b# dev.py文件内容——定义除法运算def dev(a, b): return a / b 我现在要做的是,在main.py文件里,导入test包里四个模块,调用各自模块中对应的函数,有以下几种调用方式: 123456789101112131415161718192021222324252627282930# main.py的文件内容import test.add # 导入test包下的add模块import test.sub as sb # 导入test包下的sub模块,并重命名为sbfrom test import mul # 从test包中导入mul模块from test.dev import dev # 从test包的dev模块导入dev函数,注意这里导入的是函数def calculate(x, y, operate): result = 0 if operate == '+': result = test.add.add(x, y) # 调用test.add模块中的add函数 elif operate == '-': result = sb.sub(x, y) # test.sub被重命名为sb,调用sb中的sub函数 elif operate == '*': result = mul.mul(x, y) # 调用mul模块中的mul函数 else: result = dev(x, y) # dev函数已经被导入,可以直接调用函数名 return resultprint(calculate(100, 100, '+'))print(calculate(100, 100, '-'))print(calculate(100, 100, '*'))print(calculate(100, 100, '/'))'''运行结果:2000100001.0''' 4. 包和模块导入的思考4.1 __init__.py的作用在上面的例子中__init__.py 是个空文件,是声明test文件夹是python包所必须的(主程序和包的位置在同一个目录下)。然而我们在编写main.py的主程序文件的时候,仍然要在开头导入相当多的模块,比较繁琐,这个时候可以在__init__.py中批量导入我们所需要的模块(导入包其实就是导入__init__.py文件)。 12345678910# 在__init__.py中添加如下内容import test.addimport test.subimport test.mulimport test.devadd = test.add.addsub = test.sub.submul = test.mul.muldev = test.dev.dev 123456789101112131415161718192021222324252627# main.py相应的改为如下内容from test import * # 导入包相当于执行包下的__init__.py,这个文件已经将包里的四个模块分别导入了def calculate(x, y, operate): result = 0 if operate == '+': result = add(x, y) elif operate == '-': result = sub(x, y) elif operate == '*': result = mul(x, y) else: result = dev(x, y) return resultprint(calculate(100, 100, '+'))print(calculate(100, 100, '-'))print(calculate(100, 100, '*'))print(calculate(100, 100, '/'))'''运行结果:2000100001.0''' 可以看到上面的主程序代码量少了很多,起到简化代码的作用。 4.2 if __name__ == ‘__main__‘首先来看一个现象,如果在add.py文件中不仅仅有定义函数的代码,还有编写代码时做的测试内容,如下: 12345# add.py文件中最后一行对这个函数做了测试def add(a, b): return a + bprint(add(3, 4)) 其他文件全都不变,再次运行main.py,会发现输出结果为: 123457 # add.py中测试内容也被输出2000100001.0 这显然不是我们想看到的,我们在导入add模块调用add函数的时候,并不想要其他无关的输出结果。 稍稍改变一下add.py内容 123456# add.py文件内容def add(a, b): return a + bif __name__ == '__main__': print(add(3, 4)) 此时再次运行main.py则不会输出add.py中的测试内容。 首先要了解一个概念,在每个python文件创建的时候都有一个记录名称的变量__name__,当这个python文件作为脚本直接运行,那么__name__的值为‘”__main__“;当这个文件作为模块被导入其他文件中运行的时候,这个__name__的值为模块的名字,也就是说 当.py文件被直接运行时,if __name__ == ‘__main__‘ 之下的代码块将被运行 当.py文件以模块形式被导入时,if __name__ == ‘__main__‘ 之下的代码块不被运行 在导入的模块中有选择性地执行代码,这在实际开发应用中非常普遍。 4.3 导入模块在主程序的父目录下前面的导入模块操作,导入模块要么在主程序的子目录下(加入__init__.py 声明这是一个包),要么和主程序在同一个目录(直接import),如果导入模块在主程序的父目录下,应该怎么导入呢? 首先,按照一般流程直接import导入和加入__init__.py声明都会报错找不到这个包,这里就不演示了。 其实这个问题在前面的笔记中有记录,点击这里。 当时是刚用vscode搭建python环境,对python调用一知半解都算不上,现在才有了初步的理解。 12345678910# 两种解决办法# 1.在主程序内部临时添加python运行环境路径import syssys.path.append('父目录绝对路径或者相对路径')import module# 缺点:只能调用一次(临时加入的环境变量路径),且每个想要导入的自定义模块都要写一次,比较麻烦# 2.在python安装目录下Libsite-packages中创建扩展名为.pth的文件,添加想要加入的路径。# python在遍历已知的库文件目录过程中,如果见到一个.pth 文件,就会将文件中所记录的路径加入到sys.path设置中,于是.pth文件指向的地址也就可以被Python运行环境找到了。# 这个已知的库文件目录可以通过sys.path查看。 4.4 相对导入前面3.2的例子中,包和模块的导入都是用的绝对导入(absolute import),导入时写明了工作环境中包的具体位置。 还有一种导入方式称为相对导入(relative import),还是用3.2的例子理解一下,在如下的文件结构中,主程序入口main.py和test包在同一层目录下,test包中有__init__.py(空文件),add.py和dev.py两个模块的内容如下: 123456789101112# add.py内容def add(a, b): return a + b# dev.py内容from .add import add # 相对导入,从当前导入包的目录中找到add模块def dev(a, b): return a / bdef func1(a, b): a = dev(a, b) + add(a, b) return a 上面的例子意思是,我现在要在test包的dev.py模块中用add.py模块的函数方法(同一个包中的模块相互引用,这在实际工程中很常见)。如果dev.py是主程序,我们可以直接import add;但是我们这里main是主程序,代码如下: 1234# main.py内容from test import devprint(dev.func1(1, 2)) 主程序main.py功能是导入test包dev模块,打印dev模块的函数func1(1, 2)执行结果。 如果我们在dev.py中直接导入from add import add(没有点,也就是不加当前目录),这个时候再运行main.py会报错找不到模块(因为main.py同目录下没有add.py模块)。这个时候就有两种导入方式,要么完善包名字,使用绝对导入from test.add import add;要么使用相对导入from .add import add。 这个相对导入就像是linux的文件操作方式,一个点代表当前目录,两个点代表父目录,还能用三个点表示linux无法做到的祖父目录,依此类推。 相对导入的优点就一目了然:就算改变了包的名字,这个时候调用也不会出错,也就是简化代码,方便迁移。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"编程自学","slug":"编程自学","permalink":"http://www.shelven.com/categories/%E7%BC%96%E7%A8%8B%E8%87%AA%E5%AD%A6/"}],"tags":[{"name":"python","slug":"python","permalink":"http://www.shelven.com/tags/python/"}]},{"title":"python自学笔记(6)——函数的变量和高级用法","slug":"python自学笔记(6)——函数的变量和高级用法","date":"2022-11-28T18:37:37.000Z","updated":"2022-12-03T15:54:38.000Z","comments":true,"path":"2022/11/29/a.html","link":"","permalink":"http://www.shelven.com/2022/11/29/a.html","excerpt":"前面在通过讲什么是高阶函数(能够接受函数作为参数传入的函数,或者可以返回函数对象的函数)引出了装饰器的由来和存在的意义。这里对python函数的其他基础概念做个补充和记录。","text":"前面在通过讲什么是高阶函数(能够接受函数作为参数传入的函数,或者可以返回函数对象的函数)引出了装饰器的由来和存在的意义。这里对python函数的其他基础概念做个补充和记录。 1. 函数的变量之前笔记中的例子已经对函数参数传递过程做了总结,提到了怎么调用函数的返回值,怎么实现函数的嵌套,基本概念用法都已经提过,这里只是做个思考和补充。 1.1 局部变量 函数内部的定义的变量 局部变量只在函数内部生效,不同函数可以拥有同名的局部变量,互不影响(作用域为本函数) 局部变量的作用,为了临时保存数据需要在函数中定义变量来进行存储 局部变量在函数执行时被创建,函数执行完成后,局部变量会被系统回收 1234567891011121314151617# 局部变量重名互不影响,只作用在当前函数中def added(a, b): c = a + b return cdef connect(a, b): c = str(a) + str(b) return cprint(added(4, 5))print(connect('Phantom', 'Aria'))'''运行结果:9PhantomAria''' 1.2 全局变量 函数外部定义的变量 全局变量可以在多个函数中使用(作用域为所有函数) 全局变量如果和局部变量重名,只会使用局部变量(就近原则) 如果在函数中修改不可变类型全局变量,需要使用global声明 这里有一个很有意思的现象,前面在数据类型里说过,数据可以分为可变数据类型(列表,字典,集合)和不可变数据类型(数字,元组,字符串),而python的所有参数传递都是引用传递而非值传递。因此,对于可变类型的全局变量,在函数中可以被修改;而对于不可变全局对象则无法在函数中直接修改,其本质是修改不可变数据系统会创建一个新的对象(分配一个新的内存地址),然而这个对象名已经被占用了(也就是变量名无法被指向,原来的变量名也没有被收回)。下面举个栗子。 1234567891011121314151617181920212223# 不可变类型全局变量在函数内部可以传递使用但是无法直接修改x = 100def added(): x = x + 1 # 不可变数据修改,系统会创建新的对象,而变量名x已经是全局变量的变量名,无法成为新的对象的变量名 return xadded()'''---------------------------------------------------------------------------UnboundLocalError Traceback (most recent call last)d:\\zhuomian\\python\\test.ipynb Cell 47 in <cell line: 6>() 3 x = x + 1 4 return x----> 6 added(4)d:\\zhuomian\\python\\test.ipynb Cell 47 in added(a) 2 def added(a):----> 3 x = x + 1 4 return xUnboundLocalError: local variable 'x' referenced before assignment''' 对于global是如何运作,使得python解释器可以将不可变全局变量进行修改的?这点以我的功底还无法解释……暂时只能知道是这么个用法。 顺带一提,还有个嵌套函数对外围函数的不可变变量进行修改,需要用到类似的nonlocal进行声明。 1234567891011121314151617181920212223242526x = 100def added(): global x # global声明x为全局变量 x = x + 1 print(x)added()added()def A(): y = 200 def B(): nonlocal y # nonlocal声明y为外围函数的变量(不是全局变量!) y = y + 1 return y return Btest = A()print(test())print(test())'''运行结果:101102201202''' 上面的例子本质上是一样的,对于嵌套函数来说,要修改外围函数的不可变类型的变量(看起来似乎矛盾,不可变的怎么能叫变量呢?前面已经说过,重新赋值造成数字和字符串看起来是“可变的”假象,这里分清两个变分别指什么意思),相当于是上面例子的在函数内修改不可变类型的全局变量(作用域不同,只能说相当于),只不过二者声明的方式不同。 1.3 修改可变全局变量引起的思考一个很有意思的现象: 123456789101112131415161718a = [1, 2, 3]def add1(ls): ls = ls + lsb = [1, 2, 3]def add2(ls): ls += lsadd1(a)print(a)add2(b)print(b)'''运行结果:[1, 2, 3][1, 2, 3, 1, 2, 3]''' a列表和b列表都是可变全局变量,同一个算法,为什么a在传入函数执行之后没有发生改变呢? 这里需要对可变数据类型做个回顾,可变对象可以对自身内容进行原地修改而不改变存储地址。原地修改画个重点,意思是利用方法比如reverse、sort、append等在原有对象上直接修改。 ‘=’ 是赋值语句,将右边的表达式的结果对象,引用绑定到等号左边的变量名上。赋值是创建一个新对象,赋值给目标,返回的也是新对象,引用地址会发生改变。 ‘+=’ 是增强赋值语句,对左边的对象进行原地修改,返回值为None,引用地址不变。 看到这里就能明白上面两个看似“同样”的操作为什么会返回不一样的结果,也加深了“可变”与“不可变”的理解。 2. 函数的高级用法前一篇笔记写的装饰器就是函数的高级用法之一,这里做个完善补充。 2.1 匿名函数除了用def关键字命名函数这种基础方法之外,还可以使用lambda表达式创建匿名函数。 lambda语法格式如下: 1lambda param1,...paramN:expression 匿名函数的语法比较简洁,能接受任何数量的参数但只能返回一个表达式的值。因为匿名函数比较简洁小巧,也常用在作为参数进行传递。 12345678910111213141516# 定义匿名函数func1 = lambda x, y : x + yresult = func1(1, 2)print("匿名函数func1执行结果:",result)# 匿名函数作为参数传递def func2(x, y, opt): print('函数func2执行结果为:', opt(x, y))func2(4, 5, lambda x, y : x + y)'''运行结果:匿名函数func1执行结果: 3函数func2执行结果为: 9''' 2.2 嵌套调用相比来说函数嵌套调用可能算不上是高级用法,不过这里还是补充一下。嵌套调用指一个函数里调用另一个函数,注意和嵌套函数区分。 一个简单的例子: 1234567891011121314def func1(): # 定义一个函数 print('第一个函数输出Phantom')def func2(): # 定义第二个函数 func1() # 在第二个函数里调用第一个函数功能 print('第二个函数输出Aria')func2() # 执行一个函数,实际上两个函数都执行了一遍'''运行结果:第一个函数输出Phantom第二个函数输出Aria''' 2.3 递归函数递归函数就是在一个函数内部调用自身的函数,本质上是一个循环,循环结束的点就是递归出口。 用阶乘举个最简单的例子: 1234567891011121314151617181920212223# 使用迭代实现阶乘算法def factorial(n): result = 1 for i in range(2, n +1): result *= i i += 1 return result# 使用递归实现阶乘算法def factorial_1(n): if n == 1: return 1 else: return n * factorial_1(n - 1)print(factorial(10))print(factorial_1(10))'''运行结果:36288003628800''' 迭代的方法,从1开始,进入for循环对之前的结果累积乘以 i,直至 n(上例函数被调用了1次)。 递归的方式更为直观,每次通过递减数字的方式递归调用自己(上例函数被调用了10次)。 整体上看递归更简洁明了,但是相比迭代会占用更多内存,运行时间会更长。 递归有最大深度限制,在计算机中,函数名、参数、值类型等,都是存放在栈上的。每进行一次函数调用,就会在栈上加一层,函数返回就减一层,由于栈的大小是有限的,递归次数过多就会导致堆栈溢出。 可以调用sys模块,sys.setrecursionlimit(2000)将栈的大小调整为2000,sys.getrecursionlimit()查看当前设置的最大递归深度。这种调整递归深度的方式不是无限大的,我的jupyter在调用递归函数3000次的时候就会直接退出……模块定义和调用方式后一篇笔记再说。 3. 文件操作函数3.1 open() & close()函数open()可以打开一个文件,或者创建一个新文件,函数close()可以关闭文件。两者语法如下: 12345f = open('文件名', '访问模式')f.close() # 注意最后一定要有close()with open('文件名', '访问模式') as f: # 自动调用close() f.方法() 访问模式 说明 r 只读方式打开文件,默认模式,打开文件必须存在。 w 写入方式打开文件,已存在的文件会覆盖内容(相当于linux重定向操作符>)。 a 追加方式打开文件,已存在的文件会将内容写到最后(相当于linux重定向操作符>>)。 x 只写方式打开文件,新建一个文件,若文件存在则报错。 r+ 读写方式打开文件,打开文件必须存在。 w+ 读写方式打开文件,已存在的文件会覆盖内容。 a+ 读写方式打开文件,已存在的文件会将内容写到最后。 一般用 with open() as 的方式打开文件,这种方式会自动帮我们调用f.close() 3.2 write() & read()write()向文件写入数据,以w方式访问,如果文件名存在会先清空文件内容,文件名不存在则新建;以a方式访问,如果文件名存在则续写,文件名不存在则新建;以r方式访问则报错。 read()从文件中读取数据,括号里面的参数代表读取的数据长度(字节数),如果不传入参数则读取所有数据。 readline()读取一行,同时会读取一行最后的换行符\\n,所以打印出来的时候会多一行空行。 readlines()按照行的方式读取整个文件数据,返回的是一个列表,每行数据是一个元素,同样会读到换行符\\n并且显示出来。 需要注意一点,在多次读取的操作中,后一次读取会从上一次读完的位置开始。 123456789101112131415161718192021222324252627with open('test.txt', 'w') as f: # 只写模式创建一个新文件 f.write('My name is Phantom. \\nI am Aria.')with open('test.txt', 'a') as f: # 追加模式进行续写 f.write('\\nWell, it\\'s been so long.')'''生成的test.txt内容: # 实际前面两行末尾都有换行符My name is Phantom.I am Aria.Well, it's been so long.''' with open('test.txt', 'r') as f: # 只读方式打开文件 line = f.read(1) # read读取第一个字节 print(line) line = f.readline() # readline读取第一个字节后的第一行,因为读取了换行符,所以运行结果多一行空行 print(line) line = f.readlines() # readlines读取接下来的两行,每行数据为一个元素,返回一个列表 print(line) '''运行结果:My name is Phantom. ['I am Aria.\\n', "Well, it's been so long."]''' 3.3 os模块的文件操作函数os模块和上面递归函数最后提到的sys模块用的非常多,下篇笔记再详细说明,这里就记一下用法。 这几个函数也非常直观,举个例子就知道分别有什么作用: 1234567import osos.rename('test.txt', 'TEST.txt') # 文件重命名os.remove('TEST.txt') # 文件删除os.mkdir('./test') # 创建文件夹,文件夹存在的话会报错,且只能创建一级目录os.rmdir('./test') # 删除文件夹os.makedirs('./TEST/TEST1/TEST2') #递归的方式创建多级目录","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"编程自学","slug":"编程自学","permalink":"http://www.shelven.com/categories/%E7%BC%96%E7%A8%8B%E8%87%AA%E5%AD%A6/"}],"tags":[{"name":"python","slug":"python","permalink":"http://www.shelven.com/tags/python/"}]},{"title":"python自学笔记(5)——装饰器","slug":"python自学笔记(5)——装饰器","date":"2022-11-27T19:03:55.000Z","updated":"2022-12-03T15:55:01.000Z","comments":true,"path":"2022/11/28/a.html","link":"","permalink":"http://www.shelven.com/2022/11/28/a.html","excerpt":"这里讲一讲前面提到的python装饰器,@classmethod和@staticmethod是python内置装饰器,在了解什么是装饰器之前首先要了解函数的几个特征。","text":"这里讲一讲前面提到的python装饰器,@classmethod和@staticmethod是python内置装饰器,在了解什么是装饰器之前首先要了解函数的几个特征。 1. 有关函数的几个概念1.1 函数可以接收另一个函数作为参数传入高阶函数可以接收另一个函数作为传入的参数: 12345678910111213def func1(a, b): return a + b# 高阶函数,函数func2接收函数作为参数传入def func2(func, m, n): return func(m, n)func2(func1, 1, 2) '''运行结果:3''' 从上面例子可以看到,在执行 func2函数的时候,函数对象func1作为参数被传入func2,返回func1(1, 2)的执行结果也就是3. 1.2 函数可以把另一个函数作为结果返回高阶函数也可以将函数作为结果返回: 123456789101112131415161718# 高阶函数,把函数作为结果返回def func1(): pass def func2(): # 内层函数(嵌套函数) print('执行func2函数') return func2 # 返回内层函数的引用a = func1() # 返回的函数对象func2的引用赋值给aprint(a) # 打印函数对象,获得存储地址a() # 执行内层函数func2()的功能'''运行结果:<function func1.<locals>.func2 at 0x00000288D3701750>执行func2函数''' 上面的例子可以看到,外围函数 func1将内层函数 func2的引用赋值给a,此时a就有了内层函数func2 的方法,此时打印的a是函数的存储地址,执行a() 就可以执行func2 函数的功能。 1.3 嵌套函数可以引用外层函数的变量稍稍修改1.2的例子,在外层函数添加局部变量msg: 123456789101112131415def func1(): # 外围函数 msg = 'I am Phantom' def func2(): # 内层函数(嵌套函数) print(msg) return func2a = func1() # 实际上这里获得的就是一个闭包a() # 引用外层函数的变量,执行内层函数func2()的功能'''运行结果:I am Phantom''' 这里先引用闭包的概念: 闭包:指延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量。概念比较晦涩,简单来说就是嵌套函数引用了外层函数的变量。 这个例子和上个例子唯一的区别是,msg是一个在外围函数中的局部变量,在print_msg()函数执行之后应该就不会存在了。但是嵌套函数引用了这个变量,将这个局部变量封闭在了嵌套函数中,这样就形成了一个闭包。 有了以上关于高阶函数和闭包的概念后,就可以开始理解什么是装饰器以及装饰器的作用了。 2. 装饰器decorator装饰器的本质就是一个闭包,把一个函数当做参数然后返回一个替代版函数(函数的引用)。 2.1 @标识符将装饰器应用到函数下面将用代码方式简单演示装饰器是怎么应用的。 1234567891011121314151617181920def func1(func3): def func2(): print(f'被装饰的函数{func3.__name__}即将执行') func3() # 被装饰的函数 print(f'被装饰的函数{func3.__name__}执行结束') return func2def funcx(): print('函数正在运行')a = func1(funcx) # 1a() # 2'''运行结果:被装饰的函数funcx即将执行函数正在运行被装饰的函数funcx执行结束''' 上面这个例子就是不用@标识符的装饰器,首先我定义了一个函数func1,它只有一个func3参数,这个函数里面定义了一个嵌套函数func2。func2的作用是调用func3前打印一串字符,然后执行被装饰的函数func3,结束之后再打印一串字符。 我们再定义一个测试函数funcx,功能是打印一段“函数正在运行”的字符串。 在1处,函数func1中传入函数funcx,返回函数func2的引用赋值给变量a,此时并没有执行函数,也不会有打印结果。在2处执行了func2函数,前面传入的函数funcx作为参数在原先的func3处执行,这个时候就会依次输出三行字符串。 将@标识符应用到函数上,只需要在函数定义前加上@和装饰器的名称即可。 1234567891011121314151617181920def func1(func3): def func2(): print(f'被装饰的函数{func3.__name__}即将执行') func3() # 被装饰函数 print(f'被装饰的函数{func3.__name__}执行结束') return func2@func1def funcx(): print('函数正在运行')funcx()'''运行结果:被装饰的函数funcx即将执行函数正在运行被装饰的函数funcx执行结束''' 这里@func1就是装饰器,它接受被装饰的函数作为参数传入,返回内部嵌套函数的引用(注意这个时候并没有执行函数),内部嵌套函数func2持有被装饰函数func3的引用。 可以看到@语法只是将函数传入装饰器函数,并不是什么特别难理解的概念,主要作用就是节省代码量(避免了再一次的赋值操作)。 2.2 带参数的装饰器前面示范的是不带参数的装饰器,带参数的装饰器也是类似的,我们只要知道装饰器最终返回的一定是嵌套函数的引用。在前面的参数传递博文中,我们说过*args和**kargs可以以包裹传递的方式传递不定长参数,这里也是一样的。 12345678910111213141516171819202122232425262728def func1(func3): def func2(*args, **kargs): print(f'被装饰的函数{func3.__name__}即将执行') func3(*args, **kargs) print(f'被装饰的函数{func3.__name__}执行结束') return func2@func1def funcx(a, b): print(a + b)@func1def funcy(a, b, c): print(str(a) + str(b) + str(c))funcx(1, 2)print('*************************')funcy('Phan', 't', 'om')'''被装饰的函数funcx即将执行3被装饰的函数funcx执行结束*************************被装饰的函数funcy即将执行Phantom被装饰的函数funcy执行结束''' 上面的装饰器带的参数都是我们后面自定义函数里的参数,装饰器的语法允许我们在调用时提供其他参数。 1234567891011121314151617181920212223242526272829303132#import functoolsdef func1(text): def decorator(func): # @functools.wraps(func) def func2(*args, **kwargs): if text == 'Phantom': print('%s 正在运行' % func.__name__) print(*args) print(text) return func(*args, **kwargs) return func2 return decorator @func1(text = "Phantom")def funcx(a): print(funcx.__name__)funcx('test')'''注释的运行结果:funcx 正在运行testPhantomfunc2*************************去掉注释的运行结果:funcx 正在运行testPhantomfuncx''' 先不看导入的模块,后面再解释。 上面的例子看上去很复杂,可以一层一层剥开理解。func1是允许带参数的装饰器,实际上是原有装饰器decorator的再一次封装,并且返回了这个装饰器,可以理解为含有一个形参text的闭包。当我们使用@func1(text = “Phantom”)时,python解释器将我们的实参“Phantom”传入到装饰器的环境。 而嵌套函数func2在检查到传入的text参数与字符串“Phantom”相同时,就会执行后面的打印函数名、funcx传入的实参和func1传入到decorate的实参。 通过特殊属性__name__可以看到,funcx函数指向了装饰器内部定义的func2函数,也就是经过装饰器装饰后丢失了原函数的元信息,我们真正调用的是装饰后生成的新函数。那么是不是每次都要使用func2.__name__ = func.__name__这样的代码来保留原函数信息呢?并不是,我们可以使用functools库中的@functools.wraps()来保留原函数的属性,其实这种保留只是将原始被装饰的函数的属性拷贝给了装饰函数,如果不干这件事,有些依赖函数签名的代码执行就会出错,感兴趣的小伙伴可以继续探究~ 2.3 内置装饰器上面说的@functools.wraps()其实也是内置装饰器,下面介绍其他几个常用的内置装饰器。 2.3.1 @property这个内置装饰器用来装饰类函数,被装饰的类函数不可以在类被实例化后调用,只能通过访问与函数同名的属性进行调用(也就是把类的方法伪装成属性)。 12345678910111213141516171819class A(): def func1(self): print('Phantom') @property def func2(self): print('Aria') a = A() # 实例化一个对象a.func1() # 通过实例化对象访问类方法a.func2 # 通过实例化对象将类方法伪装成属性调用'''运行结果:PhantomAria''' 我们知道属性是可以被赋值的,但是经过property装饰的方法不可以像普通属性那样被赋值。 这个特性很有意思,我们可以实现对python类私有属性的安全访问(再次强调不存在严格意义的私有属性)。 1234567891011121314151617181920212223242526272829class A: __number = 'Phantom' # 类内的私有属性 @property def number(self): return self.__numbera = A()try: print(a.__number) # 尝试直接访问类内的私有属性失败except: print("访问私有属性失败")try: print(a.number) # 通过类方法伪装的属性访问私有属性成功 print("访问私有属性成功")except: passtry: a.number = 1 # 类方法伪装的属性无法被赋值except: print("修改私有属性失败")'''运行结果:访问私有属性失败Phantom访问私有属性成功修改私有属性失败''' 2.3.2 @classmethod直接翻译,这个装饰器就是用来定义类方法的,被装饰的函数必须有一个cls参数用来绑定类本身,隐式地将类作为对象,传递给方法,调用地时候不需要进行实例化。 如果不加这个装饰器,必须要使用self参数,隐式地将类实例传递给方法,也就是说必须要实例化。 强调一点,这里地cls和self只是为了方便编程的时候一眼看出来绑定的是类还是对象,都可以用别的xxx名字代替(但是不建议)。 12345678910111213141516class A(): def func1(self,x,y): # 实例方法 return x * y @classmethod def func2(cls,x,y): # 类方法 return x * y print(A().func1(5,5)) # 必须实例化A()之后通过实例化对象才可以调用方法print(A.func2(5,5)) # 不需要实例化,直接通过类对象调用'''运行结果:2525''' 由于被classmethod装饰的函数强制暴露了类自身,所以我们可以通过被classmethod装饰的函数对类的静态变量进行一定操作,在实例化之前和类进行交互。还有类方法可以通过实例对象或者类对象去访问,所以有一个用途就是通过实例调用类方法实现对类属性的修改(点击见第三篇博客例子)。 2.3.3 @staticmethod前面博客介绍过,这个装饰器是声明静态方法的,静态方法和上面的类方法一样,不需要实例化就可以直接调用,但是这个方法不强制要求传递参数,无法直接使用任何类变量、类方法或者实例方法、实例变量(这里要注意,只有主动传参才可以调用,因为主动传参是可以按照逻辑去找需要的参数的)。 1234567891011121314151617class People(): name = 'Phantom' def __init__(self, name = 'Aria'): self.name = name @staticmethod def getName(): print('静态方法调用类属性', People.name) #print(self.name) #不能调用实例的属性,会报错,名义上是类方法,实际已经和类无关p = People()p.getName() # 可以通过 类.方法名 或者 实例.方法名 进行调用 '''运行结果:静态方法调用类属性 Phantom''' staticmethod更像是与实例无关但与类封装功能有关的函数,如果有一个功能实现的方法比较独立,可以考虑用静态方法来实现。 在继承类中,staticmethod和classmethod有以下区别 子类的实例继承了父类的@staticmethod静态方法,调用该方法,还是调用的父类的方法和类属性。 子类的实例继承了父类的@classmethod类方法,调用该方法,调用的是子类的方法和子类的类属性。 1234567891011121314151617181920212223242526272829class A(): name = 'Phantom' @staticmethod def func1(): return print(A.name) @classmethod def func2(cls): return print(cls.name) class B(A): name = 'Aria'a = A()a.func1()a.func2()print('***********************')b = B()b.func1()b.func2()'''运行结果:PhantomPhantom***********************PhantomAria''' 上面这个例子可以看出来,@classmethod装饰后的func1函数实际上已经和父类没什么关系了,尽管在父类方法里但也可以当作是个独立的函数,不管子类的实例化还是父类的实例化都是调用同一个函数,输出结果一致。而@classmethod装饰后的func2函数,cls参数绑定了类本身,子类在实例化后继承了父类@classmethod类方法,但是调用的是子类的方法和类属性。 所有装饰器存在的意义都是为函数扩展功能,总结以下几点: 装饰器通过高级函数、嵌套函数和闭包实现 装饰器返回闭包函数的引用,这个闭包函数引用中有被装饰函数的引用 装饰器通过语法糖 @ 修饰 装饰器不修改原函数和调用方式(调用的是装饰后的新函数)","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"编程自学","slug":"编程自学","permalink":"http://www.shelven.com/categories/%E7%BC%96%E7%A8%8B%E8%87%AA%E5%AD%A6/"}],"tags":[{"name":"python","slug":"python","permalink":"http://www.shelven.com/tags/python/"}]},{"title":"python自学笔记(4)——面向对象编程(下)","slug":"python自学笔记(4)——面向对象编程(下)","date":"2022-11-26T15:59:03.000Z","updated":"2022-12-03T15:55:35.000Z","comments":true,"path":"2022/11/26/a.html","link":"","permalink":"http://www.shelven.com/2022/11/26/a.html","excerpt":"前面说到python中一切皆为对象,面向对象是python的核心,也通过代码方式了解了什么是类和对象、属性和方法以及具体的分类。这篇笔记主要记录下前面没讲完的面向对象编程具体的三个特征。","text":"前面说到python中一切皆为对象,面向对象是python的核心,也通过代码方式了解了什么是类和对象、属性和方法以及具体的分类。这篇笔记主要记录下前面没讲完的面向对象编程具体的三个特征。 面向对象编程的特征python是面向对象的语言,支持面向对象的三大特征:封装(隐藏),继承和多态。 1. 封装(隐藏)1.1 封装概念隐藏对象的属性和实现细节,只对外提供必要的方法。相当于将“细节封装起来”,只对外暴露“相关调用方法”。 通过私有属性、私有方法(都是在属性或者方法前加上__来实现私有化,类外部不能直接访问)的方式,实现封装(Encapsulation)。封装的概念类似权限控制,有些属性或方法只想于类别内部使用,而不想公开于外部,除了减少代码因来源端不适当的使用发生问题外,也可保护其中重要的商业逻辑。 当然,前面说过python没有严格意义上的访问控制限制,更多还是靠编程人员的自觉= = 2 继承2.1 继承概念继承是创建新类的方式,是实现代码复用的重要手段(比如一个新类继承自设计好的类,就直接具备已有类的特征,减少代码重复编写)。对于已有的类,我们称为父类或基类,而要创建的新类,我们称为子类或派生类。python支持多继承,也就是新建的类可以有一个或者多个父类。 python3中默认继承object类,object是根类,是所有类的父亲。编写过程中object可以省略。 12345678910111213141516171819202122232425262728# 定义一个父类(object可省)class Animal(object): def __init__(self, name, color): self.name = name self.color = color def eat(self): print('%s在进食' % self.name)# 定义一个子类,括号中为父类的名字class Pig(Animal): def setName(self, newname): self.name = newname return self.namePeggy = Pig('猪', '粉色') # 实例化对象print('Peggy是%s,颜色是%s' % (Peggy.name, Peggy.color)) # 查看对象属性Peggy.eat() # 调用父类方法print('现在Peggy的名字叫做%s' % Peggy.setName('George')) # 调用子类方法print(Pig.__mro__) # 查看类的继承层次结构,可以用类属性__mro__或者类方法mro()'''运行结果:Peggy是猪,颜色是粉色猪在进食现在Peggy的名字叫做George(<class '__main__.Pig'>, <class '__main__.Animal'>, <class 'object'>)''' 从上面的例子可以看到,子类Pig从父类Animal中继承了__init__()方法,从子类中实例化对象Peggy是可以调用父类的方法的。 需要注意: 私有的属性和方法(前面带有__)不能被子类继承,也不能被访问! 2.2 多继承顾名思义一个子类继承自多个直接父类,这样也就有了多个父类的特点。 123456789101112131415161718192021# 定义一个父类马class Horse: def output(self): print('骡子的一半基因来自马')# 定义一个父类驴class Donkey: def output(self): print('骡子的一半基因来自驴')# 定义一个子类骡子,继承自马和驴class Mule(Horse, Donkey): passa = Mule() # 实例化一个对象骡子a.output() # 调用同名父类方法print(Mule.__mro__)'''运行结果:骡子的一半基因来自马(<class '__main__.Mule'>, <class '__main__.Horse'>, <class '__main__.Donkey'>, <class 'object'>)''' 上面的例子可以看到,子类骡子(Mule)继承自父类马(Horse)和驴(Donkey),这样可以拥有两个父类各自的特征。但是,如果父类中如果有同名的方法(这里的output(self)),那么子类只会从左到右的顺序,调用先继承的父类(Horse)中的方法。 同样可以通过类属性__mro__来查看继承结构,显示结果也是从左到右的顺序,从子类开始一层层往上到父类,这就是继承的顺序。 一般情况下不建议用多继承(一个人不可能有两个爹),代码可读性会变差。 2.3 重写父类方法重写的意思是,当子类中有一个和父类相同的名字的方法,子类中的方法会重新定义覆盖掉父类中的方法。 123456789101112131415class Animal: def eat(self): print('是个动物都会进食')class Pig(Animal): def eat(self): print('只有猪才会吃了睡睡了吃')Peggy = Pig()Peggy.eat() # 调用父类同名方法,子类的方法会覆盖'''运行结果:只有猪才会吃了睡睡了吃''' 如果想要在子类方法中调用父类的同名方法,最简单的实现方式是在子类方法中进行类调用,但是父类名如果修改过,在多继承时子类的方法也要重复改很多次,python为了解决这个问题引入了super()函数,需要注意super()代表父类的定义,而不是父类对象。 123456789101112131415class Animal: def eat(self): print('是个动物都会进食')class Pig(Animal): def eat(self): super().eat() # super()代表父类名,即使父类名改变这里也不需要改Peggy = Pig()Peggy.eat()'''运行结果:是个动物都会进食''' 一般而言,super在继承中经常用来继承父类的初始化方法,例如 super().__init__() 3 多态3.1 多态概念多态指不同对象对同一个方法调用,可能会产生不同的行为。举个栗子,对于同样一个吃饭的方法,不同对象比如中国人用筷子吃饭,印度三哥用手抓饭,欧美人用刀叉吃饭。 需要注意以下几点: 多态是方法的多态,属性没有多态 多态存在的必要条件:继承和方法重写 3.2 代码演示多态和“鸭子类型”12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152class People: passclass Chinese(People): def eat(self): print('中国人用筷子吃饭')class Indian(People): def eat(self): print('印度人用手抓饭')class American(People): def eat(self): print('欧美人用刀叉吃饭')# 分别实例化,并定义一个统一的接口来使用XiaoMing = Chinese()ASan = Indian()George = American()def Eatting(self): self.eat()Eatting(XiaoMing) # 相当于调用了XiaoMing.eat,以下同理Eatting(ASan)Eatting(George)print('*******************************************')# “鸭子类型”# 定义三个不同的类(实际上也都继承自根类object)class Chinese: def eat(self): print('中国人用筷子吃饭')class Indian: def eat(self): print('印度人用手抓饭')class American: def eat(self): print('欧美人用刀叉吃饭') People_list = [Chinese, Indian, American] # 封装好的类作为People_list的元素for person in People_list: person().eat() # person()是实例化对象的过程,分别调用不同类的同名方法 '''运行结果:中国人用筷子吃饭印度人用手抓饭欧美人用刀叉吃饭*******************************************中国人用筷子吃饭印度人用手抓饭欧美人用刀叉吃饭''' 不同对象调用同名方法,产生不同结果,这就体现了多态性,好处在于增强了程序的灵活性和可扩展性。 Python崇尚的“鸭子类型”就是动态类型的风格:“当看到一直鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以称为鸭子。”这种动态风格中,一个对象的有效语义,不是由继承自特定的类或实现特定的接口,而是由当前方法和属性的集合决定,也就是说,我们并不关心对象是什么类型,而是关心对象是怎么使用的。 总而言之,这种动态类型使得编程非常灵活,可以避免一些重写和继承,省去复制大量重复代码的操作。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"编程自学","slug":"编程自学","permalink":"http://www.shelven.com/categories/%E7%BC%96%E7%A8%8B%E8%87%AA%E5%AD%A6/"}],"tags":[{"name":"python","slug":"python","permalink":"http://www.shelven.com/tags/python/"}]},{"title":"python自学笔记(3)——面向对象编程(上)","slug":"python自学笔记(3)——面向对象编程(上)","date":"2022-11-25T15:31:53.000Z","updated":"2022-12-03T15:55:58.000Z","comments":true,"path":"2022/11/25/a.html","link":"","permalink":"http://www.shelven.com/2022/11/25/a.html","excerpt":"面向对象是Python的核心概念,一开始在这些概念问题上一直绕不清,这里做个简单记录。","text":"面向对象是Python的核心概念,一开始在这些概念问题上一直绕不清,这里做个简单记录。 1. 面向对象编程使用计算机语言编写代码时,有两种思路分别是面向过程编程和面向对象编程 面向过程:根据业务逻辑从上到下,直接分析解决问题的步骤,调用函数实现。强调怎么去做 面向对象:将问题分解成若干“对象”,建立对象是为了描述某个事物在解决问题过程中的行为。强调谁去做 面向过程注重步骤和过程,所有步骤从到到尾逐步实现,将功能独立的代码封装成函数,最后完成代码就是按照顺序地调用不同函数。 面向对象注重对象和职责,确认职责,根据职责确定不同对象,对象内部封装不同的方法,最后完成代码是按照顺序让不同对象调用不同方法。 python是面向对象编程思想的一门语言,包括做机器学习或者深度学习用的PyTorch、TensorFlow都是面向对象的思想,里面封装了非常多的方法,我们甚至可以不知道方法具体实现的过程和原理,直接调用函数就可以(初学的我就是一开始依葫芦画瓢,程序能跑通但是不能解释实现的原理),对于小白的入门学习确实提供了极大便利(然后一出问题就开始恶补基础了)。 2. 概念性名词先要了解概念性的专业名词,再通过代码的方式加深自己的理解。 面向对象有三个特性,封装性、继承性和多态性(下一篇博客再细说)。 封装性:把属性和方法放在一个类里面,可以通过访问类的权限属性区分开,不想释放的功能搞成私有机制 继承性:把实现好的代码和方法通过继承的方法拿过来用,节省代码量 多态性:同一个方法用不同的方式去实现,体现的多态性 先解释一下上面提到的几个专有名词: 对象(object):python中一切皆对象,对应现实生活中,任何事物都可以称为对象,有自己独特的特征。对象是通过类创建出的真实的个体(对象是类的实例化),对象由属性和方法组成。 类(class):具有同种属性的对象,现实世界中具有共同特征的事物为一类,比如人类,植物类等,描述的是所有对象的共有特征。拥有相似属性和行为的对象都可以抽象出一个类。 属性(attribute):属于对象静态的一面,描述对象的一些静态特征,比如小明的身高、体重、年龄等。 方法(method):属于对象动态的一面,描述对象的动态特征,比如小明会说话,会码代码等。 实例化:对象由一个别名叫“实例”,通过类创建对象的过程为“实例化”。 抽象:由相同特征的对象抽取共同特征的过程为“抽象”。 3. 代码方式理解类和对象开头的class来创建一个新的类,class之后为类的名称(通常首字母大写)并以冒号结尾。 12345678910111213141516171819# 定义类class People: # 定义方法 def getPeopleInfo(self): print('名字:%s, 年龄:%d' %(self.name, self.age))# 实例化一个对象 Phantom = People()Phantom.name = 'Phantom' # 使用 . 的方法添加类属性Phantom.age = 26Phantom.getPeopleInfo() # 使用 .函数名() 的方法调用类中创建的函数print(Phantom.age) # 打印实例Phantom的年龄属性'''运行结果:名字:Phantom, 年龄:2626''' 在创建的类中定义方法,而类中的方法和普通的函数有一个区别——必须有一个额外的第一个参数名称, 按照惯例是 self。self指的是实例的本身,指向当前创建对象的内存地址。某个对象调用其方法时,python解释器会把这个对象作为第一个参数传递给self,所以我们只需要传递后面的参数。 python是没有方法的重载的,如果定义了多个重名的方法,只会生效最后一个! 在上面的例子里我给Phantom添加了两个对象属性:name和age,但是如果再实例化一个其他对象,能否在创建时就给予属性而不用重新添加呢?答案是肯定的,这个时候我们可以用__init__()函数来定义属性的默认值。 1234567891011121314151617181920212223242526class People: # 初始化函数,使对象的属性具有默认值 def __init__(self, sex = 'male', age = 26): self.sex = sex self.age = age # 定义类方法 def getPeopleInfo(self): print('性别:%s, 年龄:%d' %(self.sex, self.age)) # 创建对象Phantom,不传参,属性使用默认值Phantom = People()print(Phantom.sex, Phantom.age)Phantom.getPeopleInfo()# 创建第二个对象Aria,传参,新的参数代替默认值Aria = People('Female', 24)print(Aria.sex, Aria.age)Aria.getPeopleInfo()'''运行结果:male 26性别:male, 年龄:26Female 24性别:Female, 年龄:24''' 可以看到上面创建对象Phantom后,我没有传入参数,python解释器立刻调用了__init__()函数给与了两个属性sex和age,这个时候再调用类内的方法getPeopleInfo(),就会将属性的默认值作为实参传入。 __init__(self)中只有一个默认参数self,如果创建对象传入了两个实参,那么除了self以外还需要两个形参,比如__init__(self, sex, age)这个和自定义创建的类方法不一样,一定要做区分,后面会说到。这里的self是不需要我们传递的,python解释器会自动把当前对象的引用传递进去。 4. 代码方式理解属性和方法4.1 类属性类拥有的属性分为公有属性(public)和私有属性(private),python对于类的属性没有严格的访问控制限制,这与其他面向对象语言有所区别。 _xxx 保护属性,python编辑器不会做任何处理,是给程序员看的,不希望被外部访问 xxx 自己定义的公有属性 __xxx 类中的私有属性,不能从外部直接访问,但是可以通过 实例._类名__私有属性 的方式访问 再次强调,python不存在严格意义上的私有属性。 12345678910111213141516171819# 创建类,object是对象,可以省略class People(object): name = 'Phantom' # 公有的类属性 __age = 26 # 私有的类属性 _sex = 'male' # 保护的类属性 def __init__(self): pass # 空语句,占位用,不会执行任何操作p = People()print(p.name) # 通过实例对象访问公有类属性print(p._People__age) # 通过实例访问私有类属性print(p._sex) # 访问保护的类属性(可以访问但是不推荐)'''运行结果:Phantom26male''' 4.2 实例属性实例属性是从属于实例对象的属性。 实例属性可以在__init__()方法中通过 self.实例属性名 = 初始值 的方式进行定义 实例属性可以修改、新增和删除,不会影响到类属性 123456789101112131415class People(object): name = 'Phantom'p = People()print(p.name, People.name) # 通过实例查看实例属性,通过类对象查看类属性p.name = 'Aria' # 修改实例属性print(p.name, People.name)People.name = 'Aria' # 修改类属性print(p.name, People.name)'''Phantom PhantomAria PhantomAria Aria''' 可以看到通过一个实例对象去引用修改,只是修改了实例属性而不会影响到类属性。 4.3 特殊属性Python 对象中包含了很多双下划线开始和结束的属性,这些是特殊属性,有特殊的用法。 特殊方法 含义 obj.__dict__ 对象的属性字典 obj.__class__ 对象所属的类 class.__bases__ 类的基类元组(多继承) class.__base__ 类的基类 class.__mro__ 类层次结构 class.__subclasses__ 子类列表 实际操作运行几个简单的例子: 12345678910111213141516171819202122class People: name = 'Phantom' # 类属性 def __init__(self, sex, age): # 实例属性 self.sex = sex self.age = agep = People('male', 26)# dict 生成类属性信息的字典和实例对象属性信息的字典print('People类属性为:' + str(People.__dict__))print('实例对象p属性为:' + str(p.__dict__))# class 对实例对象查询所属类信息print('实例对象p所属类信息:' + str(p.__class__))'''运行结果:People类属性为:{'__module__': '__main__', 'name': 'Phantom', '__init__': <function People.__init__ at 0x000001A7D212AB00>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None}实例对象p属性为:{'sex': 'male', 'age': 26}实例对象p所属类信息:<class '__main__.People'>''' 4.4 实例方法和类方法在类中以def开头定义的方法都是实例方法,实例方法的特点是必须有一个以上的参数(self),用于指定这个方法的实例对象。 类方法也是最少需要一个参数(cls),是类对象有的方法,需要使用装饰器@classmethod来标识其为类方法,关于装饰器的概念我后面再写一篇博客,这里简单按照字面意思理解一下。类方法可以通过实例对象或者类对象去访问,有一个用途就是通过实例调用类方法实现对类属性的修改。 1234567891011121314151617181920class People(): name = 'Phantm' @classmethod def getName(cls): return cls.name @classmethod def setName(cls, name): cls.name = namep = People()print(p.getName(), People.getName()) # 通过实例和类对象调用类方法,先查看一下类属性p.setName('Aria') # 通过实例调用类方法改变类属性print(p.getName(), People.getName())'''运行结果:Phantm PhantmAria Aria''' 4.5 静态方法python是动态的语言,我们可以动态地为类添加新的方法,或者动态地修改已有的方法。静态方法可以理解为不变的方法,不依赖于实例对象也不依赖于类对象,因此无论是实例对象还是类对象都可以调用。如果有一个功能实现的方法比较独立,可以考虑用静态方法来实现,静态方法需要使用装饰器@staticmethod来标识。 需要注意的是,静态方法无法使用实例的属性和方法。 1234567891011121314151617class People(): name = 'Phantom' def __init__(self, name = 'Aria'): self.name = name @staticmethod def getName(): print('静态方法调用类属性', People.name) #print(self.name) #不能调用实例的属性,会报错p = People()p.getName()'''运行结果:静态方法调用类属性 Phantom''' 4.6 特殊方法前面的普通方法都是通过 对象名.方法名() 的方式调用,和前面有特殊属性一样,python也有一些特殊方法(或者叫魔术方法),这些特殊方法在符合条件的时候自动触发,不需要调用。 因为特殊方法非常多,这里只简单记录一些常用的。 特殊方法 含义 构造类 __new__(cls, […]) 对象实例化时调用的第一个方法,第一个参数时类,其他参数传递给__init__(),决定是否使用 __init__(self, […]) 构造器,当一个实例被创建时调用的初始化方法 __del__(self) 构造器,当实例对象被销毁时调用的方法 表示类 __str__(self) 描述类或对象信息,比如打印实例化对象,返回定义内容(给人看) __repr__(self) 描述类或对象信息,比如打印实例化对象,返回定义内容(给解释器看) 访问控制类 __setattr__(self, key, value) 定义当一个属性被设置时的行为 __getattr__(self, key) 定义用户试图获取一个不存在的属性时的行为 __delattr__(self, key) 定义当一个属性被删除时的行为 __getattribute__(self, key) 定义当该类属性被访问时的行为(所有属性/方法调用都要经过这里) __dir__(self) 定义当dir()被调用时的行为 比较操作类 __eq__(self, other) 判断两个对象是否相等 __ne__(self,other) 判断两个对象是否不相等 __lt__(self, other) 定义小于号的行为:x < y 调用 x.__lt__(y) __gt__(self, other) 定义大于号的行为:x > y 调用 x.__gt__(y) 容器类 __setitem__(self, key, value) 定义设置容器中指定元素的操作,相当于 self[key] = value __getitem__(self, key) 定义获取容器中指定元素的操作 ,相当于 self[key] __delitem__(self, key) 定义删除容器中指定元素的操作 ,相当于 del self[key] __len__(self) 定义当被 len() 调用时的操作,即返回容器中元素个数 __iter__(self) 定义迭代容器中的元素的操作 __contains__(self, item) 定义当使用成员测试运算符(in 或 not in)时的操作 __reversed__(self) 定义当被 reversed() 调用时的操作 可调用对象类 __call__(self, [args…]) 使实例对象以 对象名() 的形式使用 这些特殊方法比较常用,看到知道是怎么一回事就好。容器类的特殊方法稍微解释一下,python中常用字典、元组、列表和字符串作为容器,它们都实现了容器协议,可迭代。最后一个调用对象类特殊方法写个代码描述一下 1234567891011121314151617181920class Calculate(): def __init__(self, x, y): self.x = x self.y = y def __call__(self, m, n): self.m = m self.n = n SUM = m + n return SUMa = Calculate(100, 200) print(a(111, 222)) # __call__() 将实例化对象a当作一个方法来执行print(a.x, a.y) # 实例属性并没有改变'''运行结果:333100 200''' 在上面这个例子中,首先初始化了一个Calculate实例a,调用 __init__() 方法,给与了实例属性x和y以及对应的值。但是对于实例对象a又做了调用 a(111, 222) ,实际上调用的是 __call__() 方法,传入自定义参数实现自己的逻辑,这在类实现一个装饰器的场景中比较常见。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"编程自学","slug":"编程自学","permalink":"http://www.shelven.com/categories/%E7%BC%96%E7%A8%8B%E8%87%AA%E5%AD%A6/"}],"tags":[{"name":"python","slug":"python","permalink":"http://www.shelven.com/tags/python/"}]},{"title":"python自学笔记(2)——运算符和参数传递","slug":"python自学笔记(2)——运算符和参数传递","date":"2022-11-24T09:05:33.000Z","updated":"2022-12-03T15:57:53.000Z","comments":true,"path":"2022/11/24/a.html","link":"","permalink":"http://www.shelven.com/2022/11/24/a.html","excerpt":"本篇笔记主要记录python基础的运算符和函数参数传递,以及自己学习过程的一些思考。","text":"本篇笔记主要记录python基础的运算符和函数参数传递,以及自己学习过程的一些思考。 1. 运算符1.1 比较运算符python中常见的比较运算符如下: == 检查左右两个值是否相等,相等则返回True != 检查左右两个值是否相等,不相等则返回True <> 和 != 一样,检查两个值是否相等,不相等返回True >= 字面意思,字面意思的还有<=、 < 和 > 1.2 算数运算符python常见的算数运算符: / 两个数相除,结果为浮点型 // 两个数相除,结果为向下取整的整数 % 取模,也就是两个整数相除的余数 ** 幂运算,返回乘方的结果 + 两个数相加,或者字符串相连 * 两个数相乘,或者返回重复若干次的字符串 - 字面意思,两个数相减 1.3 赋值运算符顾名思义都是在赋值的时候用到的运算符 = 常规赋值运算,运算结果赋值给变量 += 加法赋值运算,a += b等效于a = a+b 其他算数运算符都可以后面跟上=,进行运算后赋值 1.4 位运算符按位运算就是将数字转换为二进制来运算的运算形式,数值是用补码来表示和存储的,计算机用位运算符进行四则运算速度快。但是我们平常可能用不到,这里稍微记录一下。 & 按位“与”:两个值如果相应位都为1,则结果为1,否则0 | 按位“或”:两个值相应位有一个位1,结果就为1 ^ 按位“异或”:两个值相应位相异,结果为1 ~ 按位“取反”:对数据的每个二进制位取反 << 左移运算符:运算数的二进制全部座椅若干位,高位丢弃,低位补0;>>右移同理 1.5 逻辑运算符 and 逻辑“与”,两个都为True则返回True,否则False or 逻辑“或”,两个至少有一个True则返回True,否则False not 逻辑“非”,字面意思 1.6 成员和身份运算符python的成员运算符用来判断一个数据是否在指定的序列或者集合中,而身份运算符是用来判断两个变量是否引用自同一个对象。 in 成员运算符,在指定序列中找到值则返回True,否则False not in 成员运算符,在指定序列中没有找到值则返回True,否则False is 身份运算符,两个标识符是否引自同一个对象,是则返回True,否则False is not 身份运算符,两个标识符是否引用同一个对象,不是则返回True,否则False 是否引自同一个对象,简单理解就是看存储的内存位置是否一样,通过函数id()可以查看变量在内存中的存储位置。 2. 参数传递要理解参数传递的过程,首先要明白关于函数参数的两个具体概念:形参和实参 定义时小括号中的参数,是用来接收参数的,称为“形参”,可以是缺省参数、不定长参数 调用时小括号中的参数,是用来传递参数给函数的,称为“实参” 向函数传递实参的方式很多,确定传递参数个数可以使用位置实参或者关键字实参,不确定传递参数个数可以使用包裹(packing)传递的方式,来包裹位置或者关键字实参,进行参数传递。 2.1 位置实参函数调用时每个实参都要关联到函数定义中的一个形参,最简单的是按照形参的位置从左到右按照顺序传递,位置参数必须一一对应,缺一不可。 比如上面创建了一个describe_me()函数,形参定义了需要name和age两个参数,因此在调用这个函数的时候要按照顺序提供这两个参数。在上例中,从左到右实参‘Phantom’储存在形参name中,实参26储存在形参age中,参数传递本质上就是实参到形参的赋值操作。 2.2 关键字实参关键字实参顾名思义是传递函数的key-Value对,在实参中将关键字和值关联,因为这种对应关系是唯一的,在调用函数的时候就不需要考虑实参的顺序。 这种参数传递方式比较直观,能一眼看出函数调用时各个值对应的用途。 关键字实参和位置实参时可以一起使用,需要注意: 关键字实参必须在位置实参右边(写的时候位置实参优先) 对同一个形参不可重复传值 2.3 形参的缺省创建函数的时候可以给形参指定默认值(缺省)。 对于缺省形参,需要注意: 缺省参数要在非缺省参数之后(缺省形参放右边) 缺省参数是可选参数,可以不传;如果传入则按照传入的值进行运算 2.4 形参的不定长参数(包裹传递)当传入的参数个数不确定时,可以使用包裹位置参数和包裹关键字参数进行参数传递。 *+形参的方式传递参数,传入后根据参数的位置以元组形式保存 **+形参的方式传递参数,需要使用关键字,传入后以字典形式保存,形参名字是传入字典的键 上面例子形参中的args表示arguments位置参数,kargs表示key arguments关键字参数,这个是可以自定义的。 不同的参数传递方式可以混用,原则上要遵循位置参数,默认参数,包裹位置,包裹关键字的顺序。 1def func(name, age = 26, *args, **kargs) # 定义和调用都遵循这样的顺序 2.5 对传参的思考在CSDN上看到一个总结写的很好,函数的参数传递本质上是从实参到形参的赋值操作,而所有的赋值操作都是“引用的赋值”,因此Python中参数的传递都是“引用传递”,不是“值传递”。这句话初看有点难以理解,首先看看什么是引用传递和值传递: 值传递(pass by value):调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。 引用传递(pass by reference):调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。 根本区别在于引用传递不创建副本,最直接的理解方式就是通过查看对象的地址,看看传参前后对象在内存的位置是否改变。 2.5.1 可变对象的传递复习一下,可变对象有列表、字典和集合,我们这里以列表为例。 可以看到,如果传递的对象是可变对象,实际上传递的是对象的引用(不创建副本,传递前后在内存中存储位置不变),在函数中修改对象后,直接在原始对象上做了相应的修改。 2.5.2 不可变对象的传递如果传递的对象是不可变类型,比如元组,字符和数字,这里以数字为例。 可以看到,如果传递的对象是不可变对象,传进函数的时候同样是对象的引用,但是不可变对象无法修改,因此在赋值操作时,系统新创建了一个对象(和原来a的存储地址不同)进行赋值,而原始对象并没有改变。 也就是说,不可变对象的传递起到类似值传递的效果,但是实际上依然是引用传递的方式进行传参。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"编程自学","slug":"编程自学","permalink":"http://www.shelven.com/categories/%E7%BC%96%E7%A8%8B%E8%87%AA%E5%AD%A6/"}],"tags":[{"name":"python","slug":"python","permalink":"http://www.shelven.com/tags/python/"}]},{"title":"python自学笔记(1)——数据类型","slug":"python自学笔记(1)——数据类型","date":"2022-11-23T14:09:49.000Z","updated":"2022-12-03T16:03:26.000Z","comments":true,"path":"2022/11/23/a.html","link":"","permalink":"http://www.shelven.com/2022/11/23/a.html","excerpt":"这两个月经历了突然的疫情隔离,研究生开题,学术论坛,研究生创新项目等等……终于在这一周尘埃落定了,得以静下心来整理整理自己的一些学习笔记。之前我用过一些python编写的项目,我也只是依葫芦画瓢或者在demo上直接改,还没有系统性地学习过这门编程语言。这里就再记录下自己自学python的一些入门时的笔记,以及记录下几个机器学习方面的python库的使用方法。","text":"这两个月经历了突然的疫情隔离,研究生开题,学术论坛,研究生创新项目等等……终于在这一周尘埃落定了,得以静下心来整理整理自己的一些学习笔记。之前我用过一些python编写的项目,我也只是依葫芦画瓢或者在demo上直接改,还没有系统性地学习过这门编程语言。这里就再记录下自己自学python的一些入门时的笔记,以及记录下几个机器学习方面的python库的使用方法。 1. 六大数据类型很多编程语言的数据类型是相通或者有类似之处的,学习一门编程语言最基础的就是熟悉它的数据类型,python有6种标准数据类型。 1.1 Numbers(数字类型)数字类型简单来说就是数值,在python中是不可变数据类型。python的Numbers数据类型又可以分为以下几个子类型 整型(int): 通常称为整型或整数,是正或负整数,不带小数点。python3整型没有大小限制,可以当作python2的Long类型使用,不像其他编程语言有 int,smallint,short,long,longint,long 等。 浮点型(float): 浮点型由整数和小数两个部分组成,只能以十进制表示或者科学计数法表示,有长度限制。 布尔型(bool): 布尔型就是逻辑,使用True和False表示。注意一下在上下文环境中,True当做1,False被当作0。 复数型(complex): 复数型由实数和虚数部分构成,可以用a + bj或者complex(a, b)表示,a和b都是浮点型。 1.2 String(字符串)String是python中最常用的数据类型,说白了就是字符组成的一串内容。可以使用成对的单引号或者双引号(“或‘)创建字符串,用三个单引号或者双引号使字符串内容保持原样输出,可以包含特殊字符。在python中字符串是不可变变量。 1.2.1 字符串索引索引就是字符的位置序号,使用[]进行字符串索引,python有两种索引方式,下标索引越界均会报错。 正向索引:字符串长度为n,从0开始,索引值范围0 ~ n-1 反向索引:字符串长度为n,从-1开始,索引值范围-1 ~ -n 1.2.2 字符串切片切片意思就是取出字符串中你想要的内容。切片的标准写法是两个冒号加三个数字,如a[1:2:3],需要注意切片是左闭右开的取值,切片越界是不会报错的。 第一个数字表示切片的起始位置(省略就是从第 1 个字符开始,也就是0号位) 第二个数字表示切片的终止位置(不包括这个位置的字符,右开表现在这里;可省略,省略是最后一个字符结尾且包含) 第三个数字表示步长(缺省值为1,此时可以不写第二个冒号) 1.2.3 转义如果使用带有特殊字符的字符串,则需要进行转义,使用反斜杠 \\ 进行字符转义。 转义字符 描述 \\‘ 表示单引号 \\“ 表示双引号 \\n 换行 \\t 制表符(即四个空格) \\b 退格(删除前面一个字符) \\\\ 表示反斜杠 在字符串前加 ’r‘ 可以使整个字符串原样输出,不会被转义。 1.2.4 格式化输出和占位符格式化输出意思是按照格式说明所描述的文字规则进行输出,占位符的使用是格式化输出的表现形式。占位符的意思是替后面的变量占住这个位置,因此所有占位符最后都需要格式化定义占位符的映射(也就是解释占位符代表的东西)。 这里记录一下最常用的占位符 占位符 描述 %s 针对所有数据类型 %d 针对整型数据类型 %f 只针对浮点数 %.xf 浮点数精确到小数点后x位,注意有个点 可以看到两种输出方式得到的结果是一样的,使用占位符进行格式化输出更简介,且更常用。 1.2.5 常用函数python还有很多数据操作的函数,这里记录最常用的几个,以后继续补充 type(): 查看数据类型 len(): 查看字符串长度 int(): 将数据类型转换为整数,如int(“1234”)得到结果整型1234 float(): 转换为浮点数,如float(“12.34”)得到结果浮点型12.34 str(): 转换为字符串,如str(123456)得到结果“123456” 1.3 List(列表)列表数据可以存储任意一种数据类型,是python特有的数据类型,列表用来存储由多个值构成的序列,可以嵌套其他列表,是一种可变数据类型。 不同数据项之间由逗号分开,整体放在一个方括号[]里,就可以创建列表,如ls = [1, 2, 3, 4]就是一个列表。 1.3.1 修改列表元素因为列表是可变数据类型,因此可以用索引或者切片的方法修改列表中的元素。 可以使用del删除列表或者列表中索引为某个数的元素。 1.3.2 列表生成式除了直接创建列表,还可以使用列表生成式直接生成列表。 1.3.3 列表的方法函数记录一下操作列表的常用方法,这里就不演示了。 方法 描述 list.append(obj) 列表末尾添加新的对象 list.count(obj) 返回某个元素在列表中出现的次数 list.extend(seq) 在列表末尾添加另一个列表的所有元素 list.index(obj) 返回第一个匹配的的索引值 list.insert(index, obj) 在指定索引插入对象 list.pop(index) 移除指定索引的值,并返回该值 list.sort() 对原列表进行升序排序(纯数字才可以),降序需要添加reverse=True list.reverse() 反转列表元素 list.remove(obj) 移除第一个匹配的某对象 1.4 Tuple(元组)元组也是python的一种特殊数据类型,和列表很相似,但是是不可变对象。如果想创建一个全局都不变的变量,可以考虑创建元组。 元组中的元素用逗号分隔,一般要使用小括号(小括号不是必须的,只是为了方便理解和美观)。 元组中如果只有一个元素,需要在元素后加逗号。否则无法判断这是一个元组还是一个整型数据。 同样元组数据也可以进行索引和切片,这里不赘述。 1.4.1 元组和列表的互相转化元组转化列表使用list()函数,列表转化元组使用tuple()函数。 1.4.2 结合元组的列表生成式元组不能通过和列表一样的生成式来创建,但是列表生成式中可以加入元组。 1.5 Set(集合)集合是一个无序的不重复元素序列,可以使用大括号{}或者set()函数创建集合,但是创建一个空集合必须要用set()而不是{},因为{}是用来创建一个空字典的。 因为用的不多,简单记录一下,集合有三个特点 集合的元素是无序的。 如:{1, 2, 3}和{1, 3, 2}是完全相等的。 集合的元素是不重复的。 如:{1, 1, 1}只会保留一个值,打印结果为{1}。 集合的元素必须是不可变数据类型(数字、字符串和元组)。 如:{1, [1, 2]}打印结果会报错,因为列表是可变数据类型。 1.6 Dictionary(字典)字典用的比较多,其存储特点是键值对的形式出现(Key-Value),一个键对应一个值,每个键值对用冒号隔开,每对键值对用逗号隔开。字典也可以存储任意类型数据。 需要注意的一点,在字典数据类型中,键必须是唯一的,但是值可以不唯一,值可以取任何数据类型,但是键必须是不可变数据类型。 1.6.1 字典创建和修改字典数据可以通过花括号直接创建,或者通过dict()函数创建(非空)。 通过访问键来访问对应的值,添加、删除和修改的方法均类似。 1.6.2 字典的方法函数记录一下常用的字典方法函数,就不演示了,具体用到的时候可以查。 方法 描述 dict.keys() 返回所有键的列表dict_key对象,可以转换成列表、元组和集合 dict.values() 返回所有值的列表dict_values对象,也可以转换成列表、元组和集合 dict.items() 返回所有键值对的列表dict_items对象,同样可以转换成列表、元组和集合 dict.clear() 清空字典,无返回值,只剩下空字典 dict.get(key, default=None) 返回字典中指定key的value值,如果key不存在,则返回default值 dict.pop(key, default=None) 删除指定的key并返回对应的value值,如果key不存在,则返回default值 简单小结一下关于python数据类型的注意点: 1.可变数据类型:List、Dictionary、Set 2.不可变数据类型:Tuple、Numbers、String。不可变体现在索引这些变量名的元素不可被重新赋值 3.下标索引:String、List、Tuple支持下标索引,Dictionary是通过Key值索引 4.切片:String、List、Tuple支持切片操作","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"编程自学","slug":"编程自学","permalink":"http://www.shelven.com/categories/%E7%BC%96%E7%A8%8B%E8%87%AA%E5%AD%A6/"}],"tags":[{"name":"python","slug":"python","permalink":"http://www.shelven.com/tags/python/"}]},{"title":"深度学习笔记","slug":"深度学习笔记","date":"2022-09-19T16:58:05.000Z","updated":"2022-12-03T16:21:13.000Z","comments":true,"path":"2022/09/20/a.html","link":"","permalink":"http://www.shelven.com/2022/09/20/a.html","excerpt":"开坑记录一下学习人工智能(深度学习为主)的笔记,方便以后回顾学习~整理自点头教育","text":"开坑记录一下学习人工智能(深度学习为主)的笔记,方便以后回顾学习~整理自点头教育 人工智能的趋势展望1. 前沿技术Transformer模型基于自注意力机制,有效提高模型训练效率 由Google的Ashish Vaswani等人和多伦多大学的Aidan N.Gomez于2017年首次提出,是一种基于自注意力机制(在Transformer模型中起基础作用,可减少对外部信息的依赖,更擅长捕捉数据或特征的内部关系,优化模型训练结果)的深度学习模型,该模型主要由编码器和解码器构成,模型本身并行度较高,在精度和性能上均要优于传统的循环神经网络(RNN)和卷积神经网络(CNN)。Transformer模型在简单语言问答和语言建模任务上有着较好表现。 BERT模型基于Transformer Encoder构建的预测模型 由Google于2018年提出,是基于Transformer Encoder构建的一种模型。模型基本思想:给定上下文来预测下一个词。BERT模型架构是由多接口组成的Transformer编码器层,即全连接神经网络增加自注意力机制。对于序列中的每个输入标记,每个接口计算键值和查询向量,相关向量用于创建加权表示,合并同一层中所有接口输出并通过全连接层运行。每个层使用跳跃连接进行包装,之后将层归一化处理。 自监督学习将无监督问题转化为有监督问题的方法 旨在对于无标签数据,通过设计辅助任务来挖掘数据自身的表征特性作为监督信息,来提升模型的特征提取能力,将无监督问题转化为有监督问题的方法。 说到自监督就顺便说下有监督学习和无监督学习,有监督给定的结果是确定的;无监督是实际应用场景中最多的,结果不确定,根据类别未知(没有被标记)的训练样本解决模式识别中的各种问题。 类脑计算模拟大脑结构和信息加工过程,提高机器认知能力、降低运行功耗 类脑计算(Brain-Inspired Computing): 又称神经形态计算,是借鉴生物神经系统信息处理模式和结构的计算理论、体系结构、芯片设计以及应用模型与算法的总称。类脑计算可模拟人类大脑信息处理方式,以极低的功耗对信息进行异步、并行、高速和分布式处理,并具备自主感知、识别和学习等多种能力,是实现通用人工智能的途径之一。 AI大模型包含万亿量级参数的预训练模型,显著降低模型训练成本 AI大模型(Foundation Models):是指经过大规模数据训练且在经微调后即可适应广泛下游任务的模型。随着参数规模不断扩大,AI大模型在语言、视觉、推理、人机交互等领域涌现出新能力。 2. 人工智能的产业融合人工智能与元宇宙元宇宙(Metaverse):本质上是对现实世界的虚拟化、数字化过程,其主要包括基础设施、人机交互、空间计算等七层架构,其中计算机视觉、AI芯片和嵌入式AI等人工智能技术及基础设施共同助力元宇宙加速落地。元宇宙涵盖芯片、云计算、技术平台、通信、智能设备、内容服务等庞大生态系统。 人工智能与生命科学AlphaFold是由谷歌旗下DeepMind团队基于深度学习算法的蛋白质结构预测的人工智能系统,其被视作人工智能深入到生物领域的一大突破。目前AlphaFold已对98.5%的人类蛋白质结构做出预测,此外还对于大肠杆菌、果蝇、斑马鱼、小鼠等研究时常用生物的蛋白质结构进行预测。(这块比较感兴趣,有空继续了解一下) 人工智能与新冠疫情Eva是用于检测入境旅客新冠病毒的强化学习系统,其由美国南加州大学、美国宾夕法尼亚学、AgentRisk以及希腊相关专家合作开发。 2020年,Eva系统被部署到希腊所有入境口岸(机场、港口、车站等),用于识别限制新冠无症状旅客入境。(这里存疑,用算法确定新冠受检者,虽然在一定程度上能缓解新冠检测用品有限的不利情况,但是无疑会漏掉部分入境的可能感染者,一旦感染爆发得不偿失) 人工智能与半导体AI与EDA紧密融合,促使芯片PPA结果更加稳定 为使PPA优化结果更佳,同时为应对芯片安全性需求提升、设计规模攀升及工艺节点微缩等趋势,EDA厂商开始利用AI技术解决半导体芯片设计问题。在EDA中,数据快速提取模型、布局和布线、电路仿真模型、 PPA优化决策等环节均有AI技术参与。 人工智能与碳中和人工智能在预测、监测、优化三大环节赋能碳中和 当前,碳中和已获得全球超过40个国家和地区承诺,其中大部分国家宣布将于2050年左右实现碳中和目标。从整体来看,人工智能将从预测、监测、优化三大环节助力碳中和,如预测未来碳排放量、实时监测碳足迹、优化工作流程等。 人工智能与冬奥会2022年2月,第24届冬季奥林匹克运动会成功在北京举办。人工智能技术在冬奥会开幕式、比赛项目、运动员训练等多个场景实现应用,助力科技冬奥目标实现。 3. 人工智能产业发展的路径探究人工智能在“科研成果—商业化落地”过程中依然存在诸多挑战 伦理与安全人工智能发展面临隐私保护与算法合规使用等方面挑战 随着人工智能技术的高速发展与普及应用,由其产生的伦理与安全问题日益受到关注。人工智能不但延续信息技术的伦理问题,又因深度学习算法具有不透明、难解释、自适应、运用广泛等特征而在基本人权、社会秩序、国家安全等方面产生新问题。 国家间技术限制国家间技术限制阻碍人工智能技术进步 当前,开源深度学习框架、开源工具集、开源应用软件快速发展,国际间AI技术交流不断深入,但部分国家和政府间组织为保持自身AI 技术优势,限制AI技术交流。如美国在2021年6月发布《创新与竞争法案》,在AI、无人机、芯片等多个领域限制与中国合作;美国商务部于2019年10月和2020年5月将商汤科技、科大讯飞等多家中国AI公司加入其实体清单,实施投资限制;2022年白宫修订“关键和新兴技术(CET)清单”,对AI技术具体分类并实行技术封锁。欧盟则于2021年9月通过最新出口管制法规,内容涵盖人脸识别等AI技术。 上述相关政策与未来人工智能发展趋势背道而驰,不利于各国开展技术合作。 深度学习算法部分内容迁移学习将知识由源域迁移至目标域,提高机器学习效率 迁移学习(Transfer Learning,TL):是一种机器学习方法,是把已训练好的模型参数迁移到新的模型来帮助新模型训练,其核心目标是将知识从源域迁移到目标域,让机器也可以做到“触类旁通”。 迁移学习的主要优点是节省模型训练时间,且在目标域训练数据不足时,模型仍能取得较好的性能。 迁移学习的训练框架可以概括为:1)选择源模型,从可用模型中挑选出预训练模型;2)重用模型,在目标域中使用源模型进行训练;3)调整模型。模型可以在目标数据集中对输入-输出进行选择性微调,以让其适应目标任务。 实现迁移学习的方式主要包括样本迁移、特征迁移、模型迁移。目前,迁移学习主要应用在计算机视觉、自然语言处理等领域。 神经网络与卷积神经网络神经网络具有适应性简单单元组成的广泛并行互联网络 神经网络(Neural Network):由数千甚至数百万个紧密互连的简单处理节点组成,其主要包括输入层(输入数据)、中间层/隐藏层(学习复杂决策边界)和输出层(输出结果)。 神经网络可以用于回归,但主要应用于分类问题。如下图所示:输入层表示输入图像(64维向量),中间层使用Sigmoid等非线性函数对于输入层数据进行计算,输出层使用非线性函数对于中间层数据进行计算。 神经网络通过采取设置中间层的方式,利用单一算法学习各种决策边界,调节中间层数量以及层的深度,神经网络可学习更复杂的边界特征,而得出更加准确的结果。 卷积神经网络以图像识别为核心的深度学习算法 卷积神经网络(Convolutional Neural Network,CNN):由数千甚至数百万个紧密互连的简单处理节点组成,其主要包括输入层、卷积层、池化层、全连接层和输出层,适合处理图片、视频等类型数据。 1980年,日本科学家福岛邦彦提出一个包含卷积层、池化层的神经网络结构。在此基础上,Yann Lecun将BP算法应用到该神经网络结构的训练上,形成当代卷积神经网络的雏形;1988年,Wei Zhang提出第一个二维卷积神经网络:平移不变人工神经网络(SIANN),并将其应用于检测医学影像;1998年Yann LeCun及其合作者构建了更加完备的卷积神经网络LeNet-5并在手写数字的识别问题中取得成功。 卷积层:图片输入转化成RGB对应的数字,然后通过卷积核做卷积,目的是提取输入中的主要特征,卷积层中使用同一卷积核对每个输入样本进行卷积操作; 池化层:作用在于减小卷积层产生的特征图尺寸(压缩特征映射图尺寸有助于降低后续网络处理的负载); 全连接层:计算激活值然后通过激活函数计算各单元输出值(激活函数包括Sigmoid、tanh、ReLU等) 输出层:使用似然函数计算各类别似然概率。 循环神经网络与图神经网络循环神经网络用于处理序列数据的神经网络 循环神经网络(Recurrent Neural Network,RNN):是一类以序列数据(指相互依赖的数据流,比如时间序列数据、信息性的字符串、对话等)为输入,在序列的演进方向进行递归且所有节点(循环单元)按链式连接的神经网络。目前,语言建模和文本生成、机器翻译、语音识别、生成图像描述、视频标记是RNN应用最多的领域。 图神经网络用于处理图结构数据的神经网络 图神经网络(Graph Neural Networks,GNN):将图数据和神经网络进行结合,在图数据上面进行端对端的计算,具备端对端学习、擅长推理、可解释性强的特点。 图神经网络发展出多个分支,主要包括图卷积网络、图注意力网络、图自编码器、图生成网络和图时空网络等。 图神经网络的训练框架如下:首先,每个节点获取其相邻节点的所有特征信息,将聚合函数(如求和或取平均)应用于这些信息。 聚合函数的选择必须不受节点顺序和排列的影响。之后,将前一步得到的向量传入一个神经网络层(通常是乘以某个矩阵),然后使用非线性激活函数(如ReLU)来获得新的向量表示。 目前,图神经网络在许多领域的实际应用中都展现出强大的表达能力和预测能力,如物理仿真、科学研究、生物医药、金融风控等。 长短期记忆神经网络在RNN中加入门控机制,解决梯度消失问题 长短期记忆神经网络(Long Short-Term Memory,LSTM):LSTM是一种特殊的循环神经网络(RNN)。传统RNN在训练中,随着训练时间的加长和层数的增多,很容易出现梯度爆炸或梯度消失问题,导致无法处理长序列数据,LSTM可有效解决传统RNN“长期依赖”问题。 LSTM由状态单元、输入门(决定当前时刻网络的输入数据有多少需要保存到单元状态)、遗忘门(决定上一时刻的单元状态有多少需要保留到当前时刻)、输出门(控制当前单元状态有多少需要输出到当前输出值)组成,以此令长期记忆与短期记忆相结合,达到序列学习的目的 LSTM应用领域主要包括文本生成、机器翻译、语音识别、生成图像描述和视频标记等。(我前一篇博客做的tts用了Tacotron2,其编码器模块中就引入了一个双向LSTM层) 自编码器通过期望输出等同于输入样本的过程,实现对输入样本抽象特征学习 典型深度无监督学习模型包括自编码器、受限波尔兹曼机与生成对抗网络。 自编码器(Autoencoder,AE):包括编码器和解码器两部分,其中编码器将高维输入样本映射到低维抽象表示,实现样本压缩与降维;解码器将抽象表示转换为期望输出,实现输入样本的复现。自码器的输入与期望输出均为无标签样本,隐藏层输出则作为样本的抽象特征表示。 自编码器仅通过最小化输入样本与重构样本之间的误差来获取输入样本的抽象特征表示,无法保证自编码器提取到样本的本质特征。为避免上述问题,需要对自编码器添加约束或修改网络结构,进而产生稀疏自编码器、去噪自编码器、收缩自编码器等改进算法。 自编码器凭借其优异的特征提取能力,主要应用于目标识别、文本分类、图像重建等诸多领域。 生成对抗网络生成对抗网络(Generative Adversarial Network,GAN):通过使用对抗训练机制对两个神经网络进行训练,避免反复应用马尔可夫链学习机制带来的配分函数计算,明显提高应用效率。 生成对抗网络包含一组相互对抗模型—判别器和生成器,判别器目的是正确区分真实数据和生成数据,使得判别准确率最大化,生成器是尽可能逼近真实数据的潜在分布。生成器类似于造假钞的人,其制造出以假乱真的假钞,判别器类似于警察,尽可能鉴别出假钞,最终造假钞的人和警察双方在博弈中不断提升各自能力。(同样是我前面一篇博客语音合成tts中,应用的HiFiGAN就是基于GAN的声码器)","categories":[{"name":"深度学习","slug":"深度学习","permalink":"http://www.shelven.com/categories/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"人工智能","slug":"人工智能","permalink":"http://www.shelven.com/tags/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/"}]},{"title":"go-cqhttp扫码登录异常的解决方法","slug":"go-cqhttp扫码登录异常的解决方法","date":"2022-09-09T11:43:05.000Z","updated":"2023-08-04T15:08:20.000Z","comments":true,"path":"2022/09/09/b.html","link":"","permalink":"http://www.shelven.com/2022/09/09/b.html","excerpt":"假期用自己的服务器搭建了一个基于 Nonebot2 和 go-cqhttp 框架的QQ聊天机器人,使用的开源项目是绪山真寻bot(项目地址点击这里)。因为项目提供了一键安装包,这里就不详细说安装过程了,简单说下首次运行或者切换bot QQ号会碰到的go-cqhttp扫码登陆异常的问题。","text":"假期用自己的服务器搭建了一个基于 Nonebot2 和 go-cqhttp 框架的QQ聊天机器人,使用的开源项目是绪山真寻bot(项目地址点击这里)。因为项目提供了一键安装包,这里就不详细说安装过程了,简单说下首次运行或者切换bot QQ号会碰到的go-cqhttp扫码登陆异常的问题。 注:写这篇博客的时候go-cqhttp版本为1.0.0,往后的版本已适配签名服务器,本方法已不再适用,详情请看[go-cqhttp登录异常(错误码45)的解决办法](https://www.shelven.com/2023/08/04/a.html) 问题描述:首次运行或者切换bot QQ号后,go-cqhttp会要求需要登录验证,由于纯linux系统无法使用浏览器抓取滑条,因此会自动跳转到手机QQ扫码验证。 但是扫码会提示两个设备不在一个网络,无法登录。(很明显我的云端linux服务器不可能和手机能在一个网络中) 这个问题是腾讯QQ安全机制引起的,很明显是限制QQ机器人的手段,也就是你扫码的网络环境要和服务器的网络环境一致才可以登录。 解决方法:第一步 下载和运行win版go-cqhttp项目下载地址Releases · Mrs4s/go-cqhttp (github.com) 选择下载最新版本的go-cqhttp_windows_amd64,解压后有三个文件 双击exe文件,提示要在power shell中运行,确认,自动生成go-cqhttp.bat的批处理文件 双击运行go-cqhttp.bat,选择013,回车 修改生成的config.yml配置文件(主要就是改bot QQ号和密码) 修改之后再次运行go-cqhttp.bat,看到连接成功,网络没有问题即可 前面的反向代理失败统统不用管(因为我没有设置),我们只需要win版go-cqhttp提供设备登录信息文件(device.json)和密钥信息文件(session.token)即可。这两个文件特别重要,尤其是device.json,缺一个都将会导致登陆失败。 第二步 替换文件替换linux服务器go-cqhttp文件夹下的device.json和session.token(有的话替换,无的话直接加进去)文件,config文件最好不要替换,你只要改一下qq号和密码就行,防止底下设置的反向连接端口出错(很重要!!)。 重新在linux上启动go-cqhttp,问题解决。","categories":[{"name":"QQ机器人","slug":"QQ机器人","permalink":"http://www.shelven.com/categories/QQ%E6%9C%BA%E5%99%A8%E4%BA%BA/"}],"tags":[{"name":"qq bot","slug":"qq-bot","permalink":"http://www.shelven.com/tags/qq-bot/"},{"name":"go-cqhttp","slug":"go-cqhttp","permalink":"http://www.shelven.com/tags/go-cqhttp/"}]},{"title":"深度学习——语音合成tts笔记(下)","slug":"深度学习——语音合成tts笔记(下)","date":"2022-09-08T19:32:17.000Z","updated":"2022-12-03T16:20:43.000Z","comments":true,"path":"2022/09/09/a.html","link":"","permalink":"http://www.shelven.com/2022/09/09/a.html","excerpt":"(接上篇博客)做完数据集,我们手里现在有训练集(train set)和测试集(test set)音频文件,以及对应的拼音文本,接下来就可以跑Tacotron2来训练模型了。","text":"(接上篇博客)做完数据集,我们手里现在有训练集(train set)和测试集(test set)音频文件,以及对应的拼音文本,接下来就可以跑Tacotron2来训练模型了。 2. 训练模型 & 合成语音Tacotron2项目地址点击这里 HiFi-GAN项目地址点击这里 本篇博客训练模型&合成语音基于以上两个开源项目,再次感谢原作者! 2.1 Tacotron2简介简单讲一讲Tacotron2,它是由google推出的从文本中合成语音的神经网络结构,也就是一个语音合成(Text To Speech,TTS)框架,可以实现端到端的语音合成。Tacotron2与其前代Tacotron类似,比较重要的一个区别是在编码器模块中引入了一个双向LSTM层和卷积层,相比原来的CBHG堆叠结构和GRU循环层更为简洁。 模型主要由两部分组成: 声谱预测网络:特征预测网络,包含一个编码器和一个引入注意力机制(attention)的解码器,作用是将输入字符序列预测为梅尔频谱的帧序列。 声码器(vocoder):将预测的梅尔频谱帧序列转换产生时域波形样本,算是WaveNet的修订版。 原项目中的声码器我们暂时不用(上面地址提供的Tacotron 2就是没有wavenet的版本),因为有更好的工具HiFi-GAN。 代码实现详解有很多博客可以参考((16条消息) Tacotron2 论文 + 代码详解_HJ_彼岸的博客-CSDN博客_tacotron2),这里只要知道我们是用Tacotron2生成梅尔频谱,在此基础上结合我们输入的字符序列(也就是对应的拼音文本)训练模型。 特别注意一点:Tacotron 2是基于tensorflow1.5版本运行的,如果是自己电脑上配置环境的话,务必将python版本降到3.7以下!否则将会无法安装tensorflow1.5,除了tensorflow有硬性版本要求之外,其他依赖都可以安装最新版本——反复配置环境治好了我的精神内耗 如果你不想和我一样配置好几天环境的话,我推荐最好使用google colab,一键解决环境问题,下面会说到。 2.2 HiFi-GAN简介简单说下,声码器的作用就是将梅尔频谱转换成语音信号,和上面是对应的。 为什么我们没有用上面Tacotron2的声码器呢,主要原因就是现在有很多更优秀的声码器供我们选择。 早期比较有名的声码器WaveNet,它是一种自回归卷积神经网络,合成的效果非常好可以说和人类发声非常相似,但有个致命的缺点——合成速度太慢。直到2020年项目作者开发了这套基于GAN(生成式对抗网络)的神经网络声码器,从作者的论文里可以找到,HiFi-GAN在GPU上可以以比实时速度快167.9倍的速度生成22.05 kHz的语音,在CPU上可以以比自回归模型快13.4倍的速度生成语音,这就是它的牛逼之处。 HiFi-GAN主要有一个生成器和两个判别器,具体结构就不说了,知道一下生成器和两个判别器是通过对抗学习的方法训练的,新增加了两个损失函数来提高训练的稳定性和提高模型的性能。有能力的小伙伴可以看原论文(HiFi-GAN: Generative Adversarial Networks for Efficient and High Fidelity Speech Synthesis)了解详情。 需要注意一下作者使用VCTK数据集进行实验,测试了3个模型(V1、V2和V3),简单来说V1是最优模型,作者发布的预训练模型以及相应的配置文件都是以V1模型为基础的。我在这篇博客使用的HiFi-GAN模型g_02500000就是作者的预训练模型,配置文件为config.json。 HiFi-GAN预训练模型与配置文件下载地址: UNIVERSAL_V1 - Google 云端硬盘 2.2 注册谷歌colab和谷歌云盘训练模型是一件非常消耗算力的过程,因为涉及到图形处理,我们要用GPU进行加速。我的笔记本GPU非常拉跨,跑模型立马爆显存,因此我个人比较推荐白嫖谷歌colab上面的免费专业级GPU(Nvidia K80),免费用户只能用这一种GPU,至少比我的笔记本好多了。 需要注意下colab最大连接时长是12小时,12小时后会强行关闭GPU连接,因此需要注意下你是什么时候开始用GPU跑模型的,并且及时保存数据。关闭后要等待24小时才可以继续使用GPU,所以理论上可以用三个号不间断白嫖GPU资源(我特地申请了4个谷歌号),你只需要偶尔切换屏幕看下是否有谷歌的人机验证就行。 这里为什么还推荐谷歌云盘呢,是因为谷歌云盘可以挂载到colab上,这样调用文件就非常方便,及时保存不用担心数据丢失。谷歌云盘提供15GB的免费空间,如果保存模型比较频繁的话可能不够用,但是我们可以申请无限量的团队盘(共享云端硬盘)薅羊毛必备。 2.3 使用colab训练模型 & 合成语音我使用的colab笔记文件因为时间久远已经找不到出处了(后续如果找到会标注出来,向原作者致谢!),为了跑中文语音模型,自己也修改了很多参数和步骤,一一解释过于麻烦了….感兴趣的小伙伴可以看笔记文件。具体操作流程在底下的视频(或者点击此处看我的B站视频)。 Your browser does not support the video tag. 所有工程文件和资源如下: Tacotron2+HiFiGAN打包 链接:https://pan.baidu.com/s/1ngCUvifQM6ETwuG-NFQeGA 提取码:z6h3 400条派蒙语音测试集 链接:https://pan.baidu.com/s/1g0C0Ck4P_BxdTgMirKRc-g 提取码:5ew1 1800条派蒙语音训练集 链接:https://pan.baidu.com/s/1IDA4lppAJGHnophQAwkxNg 提取码:f2xk 需要提及一点,colab在2022年8月1号之后不再支持tensorflow1.5,请教大佬之后我将Tacotron2项目下超参数配置hparams.py改成如下即可正常运行: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788import tensorflow as tffrom text import symbolsclass hparams: def __init__(self) -> None: super().__init__() ################################ # Experiment Parameters # ################################ epochs = 3 #500 iters_per_checkpoint = 1000 seed = 1234 dynamic_loss_scaling = True fp16_run = False distributed_run = False dist_backend = "nccl" dist_url = "tcp://localhost:54321" cudnn_enabled = True cudnn_benchmark = True ignore_layers = ['embedding.weight'] ################################ # Data Parameters # ################################ load_mel_from_disk = False #实际上是区别用 numpy读wav ,还是用scipy读wav training_files = 'filelists/zh_audio_text_train_filelist.txt' validation_files = 'filelists/zh_audio_text_val_filelist.txt' text_cleaners = ['english_cleaners'] ################################ # Audio Parameters # ################################ max_wav_value = 32768.0 sampling_rate = 22050 #22050 filter_length = 1024 hop_length = 256 win_length = 1024 n_mel_channels = 80 mel_fmin = 0.0 mel_fmax = 8000.0 ################################ # Model Parameters # ################################ n_symbols = len(symbols) symbols_embedding_dim = 512 # Encoder parameters encoder_kernel_size = 5 encoder_n_convolutions = 3 encoder_embedding_dim = 512 # Decoder parameters n_frames_per_step = 1 # currently only 1 is supported decoder_rnn_dim = 1024 prenet_dim = 256 max_decoder_steps = 1000 gate_threshold = 0.5 p_attention_dropout = 0.1 p_decoder_dropout = 0.1 # Attention parameters attention_rnn_dim = 1024 attention_dim = 128 # Location Layer parameters attention_location_n_filters = 32 attention_location_kernel_size = 31 # Mel-post processing network parameters postnet_embedding_dim = 512 postnet_kernel_size = 5 postnet_n_convolutions = 5 ################################ # Optimization Hyperparameters # ################################ use_saved_learning_rate = False learning_rate = 1e-3 weight_decay = 1e-6 grad_clip_thresh = 1.0 batch_size = 2 #64 mask_padding = True # set model's padded outputs to padded valuesdef create_hparams(hparams_string=None, verbose=False): return hparams 2.4 注意事项 训练的epoch不是越多越好,我个人经验epoch 超过400会发生过拟合,测试集loss会越来越大,当然这和数据集有着密切的关系。过拟合具体表现为合成语音有部分字无法发音。 每个epoch自动保存模型且会覆盖谷歌云盘的原文件,因此务必要隔一段时间保存到本地,以免错过最佳模型(或者你改代码,比如50 epoch保存一次)。 对于文本的处理,需要参考Tacotron 2项目下的text文件夹中的四个文件cleaners.py、cmudict.py、numbers.py和symbols.py,我是进行了最简单的设置,可以根据自己需要更改。 如果你原封不动用的我的工程文件,想在本地运行合成语音的推理程序,务必将cleaners选择english_cleaner(否则会出现古神的低语)。 如果你是自己训练模型,个人认为筛选数据集非常重要,尽量把语气词和背景噪音去掉,否则效果会很差。 训练模型的参数可以根据GPU自行调整,batch_size是影响训练速度最大的因素,当你不确定显卡性能如何,请务必确保运行一段时间后显存没有炸(我就是运行以后直接睡觉了,醒来发现显存在运行半小时的时候炸了,我心态也炸了) 其实这个模型效果仍然不是很让我满意,有电音的问题可以用HiFi-GAN再训练过滤一下,我是直接用的官方预训练模型,因此效果会差一点。由于现在开学了要忙着搞开题,最近也没时间再优化模型了,以后有想法会继续补充。 我自己有考虑过将模型传到服务器,用服务器cpu运行推理,摆脱colab的限制,但是服务器不堪重负…一运行推理运存就炸…github上有不少前人做过纯cpu推理的GUI(比如MoeTTS),亲测可行。 哦对了,我在做这个项目的时候,发现已经有人基于VITS做了同个游戏的端到端语音合成,甚至开发公布了API…不得不感慨这些大佬真的用心了,有API就意味着有更多的使用方式。我搭了个顺风车,通过搭建QQ机器人,写了个原神语音合成插件,效果是可以指定原神任何角色合成任意想说的语音并且发在QQ群里(没有什么技术含量,内行看个笑话),有空尽量更新出来吧! 2022/9/10更新 已将插件更新至我的github仓库,地址Phantom-Aria/zhenxun_plugin_tts: 真寻bot插件,原神角色语音合成tts (github.com) 由于代码写的比较幼稚,就不申请官方插件索引了 适配绪山真寻bot 功能:指定某原神角色合成想要说的话 指令:[角色名]说/说过[文本] 2022/10/7更新由于原API已下线,此插件不再生效,后续再更新","categories":[{"name":"深度学习","slug":"深度学习","permalink":"http://www.shelven.com/categories/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"google colab","slug":"google-colab","permalink":"http://www.shelven.com/tags/google-colab/"},{"name":"Tacotron2","slug":"Tacotron2","permalink":"http://www.shelven.com/tags/Tacotron2/"},{"name":"HiFiGAN","slug":"HiFiGAN","permalink":"http://www.shelven.com/tags/HiFiGAN/"}]},{"title":"深度学习——语音合成tts笔记(上)","slug":"深度学习——语音合成tts笔记(上)","date":"2022-09-07T19:15:04.000Z","updated":"2022-12-03T16:20:24.000Z","comments":true,"path":"2022/09/08/a.html","link":"","permalink":"http://www.shelven.com/2022/09/08/a.html","excerpt":"两个月的暑假已经结束了,假期里自学了一点深度学习的内容,很多地方还是一知半解的,这里稍微记录一下。之前刷到一个很有意思的语音合成视频,抱着试试看的心态想自己做一个模型,于是给自己挖了一个大坑……涉及到深度学习的知识我还需要慢慢学,因此本篇博客重点还是记录下自己的踩坑操作原理部分以后搞明白了再更新","text":"两个月的暑假已经结束了,假期里自学了一点深度学习的内容,很多地方还是一知半解的,这里稍微记录一下。之前刷到一个很有意思的语音合成视频,抱着试试看的心态想自己做一个模型,于是给自己挖了一个大坑……涉及到深度学习的知识我还需要慢慢学,因此本篇博客重点还是记录下自己的踩坑操作原理部分以后搞明白了再更新 总的来说,我通过拆包游戏客户端获得5.6万条语音文件,通过github上的一个声纹识别项目分离其中一个角色的语音文件。接着用百度的语音识别API将语音识别为文本后,人工校正一遍文本,然后转换为拼音+音标,以此制作语音数据训练集和测试集。基于开源项目Tacotron2训练角色语音模型,经历400 epoch后初步训练成型,最后基于HiFiGAN合成语音。整个后半段流程是在google colab上完成的,为了完成模型训练我申请了4个谷歌账号…不得不说白嫖的GPU真香~ 1. 制作数据集可以说整个项目大部分时间花费在整理数据集上,根据我自己的经验,数据集的语音长度在2秒-10秒之间效果最好,数量大约在2000条左右(为了涵盖尽可能多的汉字发音)。需要注意一点,不管拆包的原语音采样率如何,都要统一重采样到22050 hz,这是Tacotron2训练模型的要求。 1.1 Extractor2.5 + vgmstream-win拆包首先是这款国内游戏的拆包,所有角色的语音文件都在目录D:\\Genshin Impact\\Genshin Impact Game\\YuanShen_Data\\StreamingAssets\\Audio\\GeneratedSoundBanks\\Windows\\Chinese下,我们使用软件Extractor2.5进行音频文件拆包。 Extractor2.5是个非常好用的游戏解包工具,我们将所有pck源文件所在目录输进去(可以批量选中文件),确定输出目录,点击开始即可。 运行结束之后可以看到这个游戏拆包有56958条语音文件…点击左下角反选,全部解压到自己的文件夹中。 但是你会发现解压出来的wav文件无法打开,需要使用vgmstream进行解密和转码(项目地址戳这里)。 可以看到vgmstream-win文件夹只有一个可执行程序test.exe,其他都是dll库文件。 这个test.exe是不能直接运行的,需要把程序拖到刚才拆包的语音文件上,但是几万条语音我们不可能一个个拖过去,因此我们在语音的文件夹下,写一个如下的批处理文件(命名为批处理.bat),运行批处理就可以了。 12345@echo offfor /r %%i in (*.wav) do ( "D:\\zhuomian\\vgmstream-win\\test.exe" "%%~nxi" #路径改成你自己的,注意路径不能有中文)pause 运行后生成的wav.wav文件就可以正常播放了,所有音频采样率均为48000Hz(采样率很重要,贯穿整个项目)。 1.2 基于Tensorflow的声纹识别这部分内容来源于github(项目地址戳这里),作者基于tensorflow做了个声纹识别模型,通过把语音数据转换短时傅里叶变换的幅度谱,使用librosa计算音频的特征,以此来训练、评估模型。因为我只用到了对比部分,因此我下载了作者预训练的模型,以及对声纹对比文件infer_contrast.py做了修改。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162import argparseimport functoolsimport numpy as npimport tensorflow as tffrom utils.reader import load_audiofrom utils.utility import add_arguments, print_argumentsimport os,shutilimport gcos.environ['TF_CPP_MIN_LOG_LEVEL']='2'parser = argparse.ArgumentParser(description=__doc__)add_arg = functools.partial(add_arguments, argparser=parser)add_arg('audio_path1', str, 'audio_db/Paimon.wav', '标准的派蒙音频') # 自己准备的标准音频,下面两个也是add_arg('audio_path2', str, 'audio_db/Klee.wav', '标准的可莉音频')add_arg('audio_path3', str, 'audio_db/Kokomi.wav', '标准的心海音频')add_arg('input_shape', str, '(257, 257, 1)', '数据输入的形状')add_arg('threshold', float, 0.8, '判断是否为同一个人的阈值')add_arg('model_path', str, 'models1/infer_model.h5', '预测模型的路径') # 作者的预训练模型args = parser.parse_args()# 加载模型model = tf.keras.models.load_model(args.model_path,compile=False)model = tf.keras.models.Model(inputs=model.input, outputs=model.get_layer('batch_normalization').output)# 数据输入的形状input_shape = eval(args.input_shape)# 预测音频def infer(audio_path): data = load_audio(audio_path, mode='test', spec_len=input_shape[1]) data = data[np.newaxis, :] feature = model.predict(data) return featureif __name__ == '__main__': # 预测的两个音频文件 feature1 = infer(args.audio_path1)[0] feature2 = infer(args.audio_path2)[0] feature3 = infer(args.audio_path3)[0] datapath = "./test2" #上传到集群的解包音频文件位置 dirs = os.listdir(datapath) for audio in dirs: personx = 'test2/%s' % (audio) featurex = infer(personx)[0] # 对角余弦值 dist1 = np.dot(feature1, featurex) / (np.linalg.norm(feature1) * np.linalg.norm(featurex)) if dist1 > args.threshold: print("%s 符合派蒙模型,相似度为:%f" % (personx, dist1)) shutil.move("./test2/%s" % (audio),"./dataset/Paimon") # 移动音频文件,路径自选 else: dist2 = np.dot(feature2, featurex) / (np.linalg.norm(feature2) * np.linalg.norm(featurex)) if dist2 > args.threshold: print("%s 符合可莉模型,相似度为:%f" % (personx, dist2)) shutil.move("./test2/%s" % (audio),"./dataset/Klee") else: dist3 = np.dot(feature3, featurex) / (np.linalg.norm(feature3) * np.linalg.norm(featurex)) if dist3 > args.threshold: print("%s 符合心海模型,相似度为:%f" % (personx, dist3)) shutil.move("./test2/%s" % (audio),"./dataset/Kokomi") gc.collect() 需要注意一点,为了提高识别的准确性,这个项目要求的语音长度不能低于1.7s,因此我用ffmpeg将所有长度低于2s的短音频全部过滤了(这里不赘述实现过程)。 之后将三个角色的标准语音分别放在audio_db文件夹下,识别的原理是通过预测函数提取三个角色的音频特征值,对5.6万条音频分别比对三个角色的标准音频特征,求对角余弦值,在多次试验后选择了对角余弦值0.8,作为判断两条语音是否为同一个人的阈值。 直接在集群上运行infer_contrast.py,相似度高于0.8的音频则会被挑选到对应的dataset文件夹中。 实际上这个声纹识别的结果仅能作为参考,不能保证百分百正确,原因有很多: 1.声优都是怪物,一个人用好多相似的声线配了不同角色,导致无法分辨出不同角色的语音(假阳性)。 2.一句话的语调不同会表现出音频特征值不同,而这个算法下会导致对角余弦值偏小,从而判断成发声的是不同的人(假阴性)。 因此识别的结果需要进行人工校正,也就是需要自己听一遍到底是不是这个角色的语音= =(最好同下一步一起进行,省时间) 这里我验证并分离出2293条长度2秒以上的派蒙语音,以其中的1820条作为训练集,473条作为测试集。后续训练模型用到的时候会说。 1.3 基于百度语音识别API的语音转文本光有语音还不行,我们要训练模型就要有对应的文本。很多单机游戏(比如柚子社的游戏)有解包脚本,可以完整解出所有资源,其中就包括语音文件和对应的文本。但是解包有客户端的游戏不同,比如这款游戏发布不同版本的客户端,文件结构就会发生很大的改变,导致以前做的文件定位统统失效,而且包括文本在内的很多文件也是加密的,无法解出(也可能是我个人问题)。 因此,我们还是需要借助语音识别的软件将语音转成文本。这里涉及到另一个问题,不管多么强大的语音转文字技术,都是在已有的数据集基础上不断训练模型而产生的,游戏中有相当多新造的词(比如中二台词,游戏人名,地点等等),这在转化文本过程中是肯定无法百分百准确的,甚至会“空耳”产生歧义。 因此转文本这一步结束后需要人工校准,至少保证读音正确。 我是在百度AI开放平台申请了语音识别API,每个账号有200万次免费调用额度,但是限制并发数2(没办法,既然是白嫖就忍忍)。 查看官方放在github上的demo,改一改就可以调用API了(每当问我不会使用的时候都是看demo然后魔改2333)。 我这里以官网提供的asr_raw.py为例,直接下载,并修改成如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112# coding=utf-8import sysimport jsonimport timeimport gcimport osimport timeIS_PY3 = sys.version_info.major == 3 # 判断你用的是python3.x还是2.x版本,推荐还是用3.xif IS_PY3: from urllib.request import urlopen from urllib.request import Request from urllib.error import URLError from urllib.parse import urlencode timer = time.perf_counterelse: import urllib2 from urllib2 import urlopen from urllib2 import Request from urllib2 import URLError from urllib import urlencode if sys.platform == "win32": timer = time.clock else: # On most other platforms the best timer is time.time() timer = time.timeAPI_KEY = 'XXXXXXXX' # 改成你自己的,下面一条一样SECRET_KEY = 'XXXXXXXX' FORMAT = "wav"; # 文件后缀只支持 pcm/wav/amr 格式CUID = '123456PYTHON';RATE = 16000; # 固定值,这里一定一定要注意采样率DEV_PID = 1537; # 1537 表示识别普通话,使用输入法模型。根据文档填写PID,选择语言及识别模型ASR_URL = 'http://vop.baidu.com/server_api'SCOPE = 'audio_voice_assistant_get' # 有此scope表示有asr能力,没有请在网页里勾选,非常旧的应用可能没有class DemoError(Exception): pass""" TOKEN start """TOKEN_URL = 'http://aip.baidubce.com/oauth/2.0/token'# 核对tokendef fetch_token(): params = {'grant_type': 'client_credentials', 'client_id': API_KEY, 'client_secret': SECRET_KEY} post_data = urlencode(params) if (IS_PY3): post_data = post_data.encode('utf-8') req = Request(TOKEN_URL, post_data) try: f = urlopen(req) result_str = f.read() except URLError as err: print('token http response http code : ' + str(err.code)) result_str = err.read() if (IS_PY3): result_str = result_str.decode() result = json.loads(result_str) if ('access_token' in result.keys() and 'scope' in result.keys()): if SCOPE and (not SCOPE in result['scope'].split(' ')): # SCOPE = False 忽略检查 raise DemoError('scope is not correct') return result['access_token'] else: raise DemoError('MAYBE API_KEY or SECRET_KEY not correct: access_token or scope not found in token response')""" TOKEN end """if __name__ == '__main__': token = fetch_token() """ httpHandler = urllib2.HTTPHandler(debuglevel=1) opener = urllib2.build_opener(httpHandler) urllib2.install_opener(opener) """ for audio in range(1,1825): AUDIO_FILE = str('/public/home/wlxie/test4voice/baiduyun/training_16K/train' + str(audio) + '.wav') #路径改成自己的 speech_data = [] with open(AUDIO_FILE, 'rb') as speech_file: speech_data = speech_file.read() length = len(speech_data) if length == 0: raise DemoError('file %s length read 0 bytes' % AUDIO_FILE) params = {'cuid': CUID, 'token': token, 'dev_pid': DEV_PID} params_query = urlencode(params); headers = { 'Content-Type': 'audio/' + FORMAT + '; rate=' + str(RATE), 'Content-Length': length } url = ASR_URL + "?" + params_query req = Request(ASR_URL + "?" + params_query, speech_data, headers) try: begin = timer() f = urlopen(req) result_str = f.read() except URLError as err: print('asr http response http code : ' + str(err.code)) result_str = err.read() #输出转文字结果 result_str = result_str.decode() result = json.loads(result_str) res = result['result'][0] print('train' +str(audio) + '.wav' + '识别结果:' + res) with open("training_1800_result.txt", "a") as of: of.write('train' + str(audio) + '.wav' + "|" + res + '\\n') # 转成“路径|文本”的格式,方便人工校准 gc.collect() 这里也有一个大坑,这个语音转文本API要求音源采样率必须是16000Hz,前面说到我们解包得到的音频是48000Hz,而且后面训练模型要求采样率为22050Hz!也就是说如果我们现在把所有音频转成16000Hz的话,势必会对训练模型产生影响(高频可以转低频,但是低频转高频语音质量不会有一丁点儿的提升),因此我这边用拆包音频做了两个备份,一个是转成16000Hz,放在training_16K文件下,专门用于语音转文本;一个是转成22050Hz,放在training_22K文件下,专门用于后续训练模型。重采样仍然用我们的老朋友ffmpeg,因为就一行命令的事这里也不赘述了。 前面也说到这个API并发数限制为2,经常是用着用着就断开了(也是我比较笨比,不会写限制并发数发送请求的代码),所以我将训练集的1825个语音写了个小脚本,重命名为train1.wav-train1825.wav,所以才用了for循环一句一句调用API转文本,到哪个地方断了也可以迅速找出来并继续。 总之效果如下,训练集1825条语音和测试集473条语音全部转换为文本,且能清晰地看到一一对应关系: 一眼看效果还不错,为了保证准确率,将txt文件传回本地,人工校正吧(语气词部分本来是要去除的,但是工作量会比较大放弃了,起码要保证发音没问题)。 这个数据集因为不是标准的普通话数据集(标准数据集可以找标贝,就有那种纯合成的标准普通话),声优也有特殊的口癖和发音,额,这是无法避免的。 1.4 基于pypinyin的汉字转拼音因为后面训练模型的Tacotron2是基于英文模型开发出来的,我们无法直接用中文文本训练。一个行之有效的方法是将中文转换成拼音+数字声调的方式,这样数据就可以顺利地被载入。 这里推荐一下pypinyin模块,该模块安装比较方便(直接用pip),也是个非常实用和高质量的汉字拼音转换工具! 我将人工校准后的txt文件传回集群,去掉前面的“|”之前的内容,再写个小脚本将所有标点符号删除,接着汉字转拼音,这里就记录下pypinyin的用法吧。 123456789from pypinyin import lazy_pinyin, Styleimport linecacheoutput_file = open("/public/home/wlxie/test4voice/baiduyun/training_pinyin.txt","w")readlist = list(range(1,1821)) # 人工校准的时候去掉了4条不是该角色的音频for i in readlist: text = linecache.getline("/public/home/wlxie/test4voice/baiduyun/cheat_training.txt",i) text = " ".join(lazy_pinyin(text, style=Style.TONE3)) output_file.write(text) 然后将拼音前按照Tacotron2训练的要求,加上了音频文件对应的colab路径(为什么用这个路径我下一篇博客再说明),以及每句话末尾加个英文的句号,最后输出结果如下: 同样的方法对测试集也转拼音,这样前期的数据集文件就制作完成啦!接下来就是重点——训练模型。下篇博客接着说完。","categories":[{"name":"深度学习","slug":"深度学习","permalink":"http://www.shelven.com/categories/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/"}],"tags":[{"name":"拆包","slug":"拆包","permalink":"http://www.shelven.com/tags/%E6%8B%86%E5%8C%85/"},{"name":"声纹识别","slug":"声纹识别","permalink":"http://www.shelven.com/tags/%E5%A3%B0%E7%BA%B9%E8%AF%86%E5%88%AB/"},{"name":"语音转文本","slug":"语音转文本","permalink":"http://www.shelven.com/tags/%E8%AF%AD%E9%9F%B3%E8%BD%AC%E6%96%87%E6%9C%AC/"}]},{"title":"frp内网穿透配置笔记","slug":"frp内网穿透配置笔记","date":"2022-07-12T19:55:04.000Z","updated":"2022-12-03T16:11:04.000Z","comments":true,"path":"2022/07/13/a.html","link":"","permalink":"http://www.shelven.com/2022/07/13/a.html","excerpt":"过一段时间要到校外学习,而学校的资源只能在校园内网下才能使用(登录集群可以看到登录ip是10开头的A类地址,无法公网ip访问)。为了方便在校外访问校园内网的集群,我手里正好也有一个备案过的服务器和域名,于是自己用frp搭建了一个反向代理服务器,穿透了校园内网,这里记录下自己搭建过程。","text":"过一段时间要到校外学习,而学校的资源只能在校园内网下才能使用(登录集群可以看到登录ip是10开头的A类地址,无法公网ip访问)。为了方便在校外访问校园内网的集群,我手里正好也有一个备案过的服务器和域名,于是自己用frp搭建了一个反向代理服务器,穿透了校园内网,这里记录下自己搭建过程。 其实一开始我打算直接用开发比较成熟的花生壳软件做内网穿透,但是不知道怎么回事,显示连接成功但是ssh远程登陆不上,后来就放弃了,最后决定用自己的服务器和域名穿透(后来我还申请了花生壳学生版,羊毛先薅到以后再说用不用)。 frp是一个go语言写的开源内网穿透和反向代理软件,支持tcp, udp, http, https等协议,支持linux、mac、windows平台,操作也很方便,非常适合我这种小白。 1 下载frp源代码作者发布在github,点击这里。 选择最新的release版本,注意frp在service端和client端有两个不同的程序和配置文件,service端是你想要做反向代理的有公网ip的服务器,client端是处于内网之下的你想要穿透的服务器。 service端和client端一定要同一个版本。这里我的service端和client端都是linux操作系统,所以我直接下载了linux_arm64.tar.gz(我想顺便远程操控实验室电脑,所以也下载了windows版本,默认windows远程桌面端口号是3389,这个以后再说)。 将tar.gz文件传到两台服务器上,tar -zxvf解压就可以使用了(不需要编译,就是这么简单)。 在service端保留frps程序和相应的ini配置文件,在client端保留frpc程序和相应的ini配置文件(主要防止自己搞错)。配置文件有两种,我们可以选择其中一个;ini是最简单的配置文件,full.ini配置文件中记录了全部配置参数和英文解释,需要的时候可以自己根据情况修改。 2 修改配置文件网上的教程很多,full.ini也记载了全部的配置方法,我这里只记录下我自己的配置(敏感信息就不展示了)。 2.1 service端配置frps.ini配置文件修改如下: 123456789101112131415[common]bind_port = 7000 # frp监听的端口,默认7000,可改bind_udp_port = 7400 # UDP通讯端口,可不设置,用于点对点穿透token = xxxxxxxx # 安全考虑需要设置口令,client端需要用到dashboard_port = 7500 # frp管理端口,可改dashboard_user = xxxx # 管理端口认证的用户名,用于身份识别,自己设置dashboard_pwd = xxxx # 管理端口认证的密码,用于身份识别,自己设置enable_prometheus = truesubdomain_host = xxx.xxx.xxx # 设置子域名,主要方便登录管理界面。不用ip地址,用域名+端口的方式直接访问log_file = /usr/local/frp/frps.log # frp日志配置,这里是记录3天的日志信息log_level = infolog_max_days = 3 子域名设置主要是方便登录管理界面,不是必须的,反正我记不住服务器一长串ip地址…这个域名需要DNS解析后才能使用 后台不挂起运行frps: 1nohup ./frps -c frps.ini & 这个时候我们是看不到运行日志的,打开刚刚设置的frps.log文件 几个设置端口都监听成功,最后也显示frps started successfully说明开启成功。 2.2 client端配置frpc.ini配置文件修改如下: 12345678910[common]server_addr = xxx.xxx.xxx.xxx # 填写你的service端服务器公网ip,这里我写我的云服务器ipserver_port = 7000 # 前面设置的frp监听端口,需要保持一致token = xxxxxxxx # 前面设置的口令[ssh] # 这里只演示ssh端口的映射,其他参考frpc_full.initype = tcp # tcp协议local_ip = 127.0.0.1 # 这个地址代表本机local_port = 22 # ssh端口,默认22,由你ssh登录的client服务器决定remote_port = 6000 # 映射的service端服务器的端口,自己定义 注意下remote_port这个设置的是service端也就是云服务器的端口,通过这个端口访问client端的22端口,也就是端口映射。 同样的后台不挂起运行frpc: 1nohup ./frpc -c frpc.ini & 打开nohup的输出文件: 显示login to service success表示和service端连接成功。 全部设置完成后,理论上我就可以通过云服务器的主机地址+6000端口,通过ssh方式访问学校内网中的集群主机地址+22端口了。 但是我的云服务器比较特殊,还需要进行一步开放防火墙端口。 3 开放serviced服务器端口如果在2.2这一步配置之后一直连不上service端,那极有可能是service服务器的端口没有开放。 特别注意一点,如果是买的云服务器(比如我买的腾讯云服务器),不仅要在控制台页面开放端口,还需要在linux云服务器开放端口。举个例子,我们这里用到的云服务器端口是7000,7400,7500和6000,首先要在控制台防火墙页面 开放这几个端口。 然后在云服务器上打开防火墙,开放对应端口: 1234systemctl start firewalld # 打开防火墙firewall-cmd --permanent --add-port=7000/tcp # 永久开放指定的7000端口(其他端口同理)firewall-cmd --reload # 重启防火墙firewall-cmd --list-ports # 查看防火墙开放的所有端口 注意一下防火墙端口设定完成后,需要重载防火墙才会生效。 我们把自己云服务器的防火墙和端口配置好就行(学校集群你不是root用户你也配置不了,一般来说也不会设置防火墙)。 4 frp管理面板有两种方式可以访问: service端服务器 ip地址:端口号 设置子域名后可以用 子域名:端口号 用户名密码认证后,可以看到如下页面: 主要就是看一下连接数量,连接方式,产生的流量等等,具体就不细说了。 开个手机热点,用xshell登陆一下集群,发现显示的登录ip变成了localhost,而不是10开头的A类地址了,说明反向代理成功。 连接速度非常快,而且稳定!以后登录集群就再也不用校园网啦! 5 写在最后这种用外网服务器做反向代理服务器,通过端口转发的方式访问内网服务器还是有一定安全风险的,该开防火墙开防火墙,小心驶得万年船。 还有,这种方法也有个缺点。打个比方如果你在校外,而学校服务器因为某种不可抗力重启了(比如停电,这在新疆真的太常见了)这就相当于你挂在后台nohup的程序被强制杀掉了。等到管理员重启后,client端的frpc程序就需要再执行一次才能生效,这个时候就只能拜托有学校集群账号的人帮你在后台执行nohup,你才能从外网访问集群。 要应对这种情况,最简单的是你写个开机自启动脚本执行frpc,但是你没有root权限是不可行的…或者你让集群管理员给你su权限,这一般来说也不太可能…如何完美解决这个问题还有待研究","categories":[{"name":"网络相关","slug":"网络相关","permalink":"http://www.shelven.com/categories/%E7%BD%91%E7%BB%9C%E7%9B%B8%E5%85%B3/"}],"tags":[{"name":"内网穿透","slug":"内网穿透","permalink":"http://www.shelven.com/tags/%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F/"},{"name":"反向代理","slug":"反向代理","permalink":"http://www.shelven.com/tags/%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86/"}]},{"title":"blastn & blastp 寻找同源基因","slug":"blastn-blastp-寻找同源基因","date":"2022-07-05T11:25:26.000Z","updated":"2022-12-03T16:11:26.000Z","comments":true,"path":"2022/07/05/a.html","link":"","permalink":"http://www.shelven.com/2022/07/05/a.html","excerpt":"今天接到一个任务,大致内容是在一个植物的全长转录组数据中找拟南芥的三个同源基因。简简单单的描述,我的想法也很简单,直接找基因的CDS序列做blastn比对就完事了,结果却没有那么顺利…记录一下踩的坑和解决办法。","text":"今天接到一个任务,大致内容是在一个植物的全长转录组数据中找拟南芥的三个同源基因。简简单单的描述,我的想法也很简单,直接找基因的CDS序列做blastn比对就完事了,结果却没有那么顺利…记录一下踩的坑和解决办法。 1 blastn寻找同源基因三个基因TAIR号是AT4G28590、AT2G43010和AT2G34640,从全长转录组测序报告中,我找到了非冗余的转录本序列文件CD-hit-est.fasta,首先第一步就是本地建核酸序列库。 1makeblastdb -in CD-hit-est.fasta -dbtype nucl -input_type fasta -out Kc 因为给的是TAIR号,所以直接去TRIR官网查找相应基因的CDS序列做比对 手动创建query gene的fa序列文件 1234vim RCB_cds.fna# 手动创建fa文件>AT4G28590ATGAGTTTCTTCGCTGTTGCTTGCTCCGCGCCAAGATCTTCTATGCTTCTCACCGGCTTGAATTCGAGCTTCTCTGATATGCATCGCAGCCCACTATTTGTTTTCCCGGTGACTATATCATCCCGGAGCGTGAAACGCTTCGCCGCTGTTTCGTCTGATTCCGTACTAGACCCTGAATCCAAAAATCAAACTCGGTCCCGTCGCAAAAATAAGGAAGCAGTTACGCCAATTGCTGAAACCGAGAACAATGAAAAGTTTCCGACAAAGGTCCCGCGTAAATCGAAGCGTGGGCGGCGGAGTGAAGCAGACGCTGTGGAAGATTACGTGAGAAGCTCCCTCGAGCGTACTTTCTCCACCATAAAGGAGCAGAATCCGGAGGTTTTTGAGAACAAGGAGAAGGCGAATTTCATCAAAGACAGAGGCGTTGATGAAGAAGAGGAAGAAGAAGAAGAGATGGTGGTGGAAGAGGAAGATCCAGATTGGCCAGTAGATACAGACGTTGGATGGGGAATCAAAGCTTCGGAGTATTTCGATACACATCCAATCAAAAACGTGGTTGGAGATGATGGGAGTGAGATTGATTGGGAAGGTGAGATTGATGATAGTTGGGTCAAGGAGATCAATTGTTTGGAATGGGAAAGCTTTGCTTTTCATCCTAGTCCACTCGTTGTCCTTGTATTCGAGCGATACAAAAGAGCTAGTGATAACTGGAAGACATTGAAGGAGCTTGAGAAAGCTATCAAAGTTTATTGGGATGCGAAAGATCGATTACCTCCACGGGCGGTTAAGATTGACCTGAACATCGAGACAGATTTGGCATATGCTCTTAAAGCTAAGGAATGCCCACAGATTCTCTTCTTACGCGGAAACCGGATTCTGTACAGGGAGAAAGACTTTCGCACGGCGGATGAATTGGTTCATATGATTGCGCATTTCTACTATAAAGCGAAGAGGCCTTCGTGTGTCGACAAGGCTAATGTAACCCCGTACTGTTAG blastn比对 1blastn -query RCB_cds.fna -out RCB_blastn_Kc.out -db Kc -outfmt 6 -evalue 1e-5 -num_threads 4 RCB_blastn_Kc.out是blast的m8格式输出文件,找到匹配长度最长的(也就是第一条)subject gene id,回到非冗余转录本,找到subject gene在哪行,最后找出转录本序列。 1234# 找到subject gene所在行(subject gene id中有所在行数,这里验证下)cat CD-hit-est.fasta | grep -n "Kc-zong_1-10k_transcript/10791"# 提取序列awk 'NR>=10719 && NR<=10720' CD-hit-est.fasta 紧接着出现一个问题:AT2G43010和AT2G34640这两个基因无法通过blastn比对找到同源序列,evalue值不管放到多宽都比对不上。 因为这个植物在NCBI上没有参考基因组,我们课题组也只测了全长转录组而没有测基因组,所以当一开始没有比对出结果的时候,我一度怀疑是这种植物压根儿就没有这俩基因,或者这个样品叶片(测序的部位)在检测的时间点就没有转录相应的基因。 本地blast找不到同源基因,我又从近缘菊科植物开始折腾,思路是如果菊科有同源基因则寻找保守结构域,设计引物将CDS区域克隆出来。至今已发表的植物基因组可以从网站Plabipd(本站网址导航栏有收录)找到,这个网站很贴心地把物种种属关系也列了出来,可以很方便地找物种学名和近缘关系。 理想很丰满现实很骨感,我从菊科一级一级往上找,直到Eudicotyledoneae(真双子叶植物分支)才用blastn比对上同源基因,而且无一例外比对上的全是十字花科(拟南芥所在科)植物,根本不算近缘物种….无奈之下试了blast的其他功能,用氨基酸序列跑了一遍blastp,然后发现菊科也有序列可以比对上了!这才打开新世界的大门 2 blastp寻找同源基因基于翻译阅读框对去冗余的全长转录本进行CDS预测(TransDecoder软件),结果以fasta格式保存,后续我会对这个文件验证一遍,先建蛋白库做blastp比对。 1makeblastdb -in transdecoder.pep.fa -dbtype prot -input_type fasta -out nr_Kc 可以看到只有25128个编码蛋白基因,对于基因组大小在1G左右的菊科物种来说,这个基因数量过少。因此后续还需要对全长转录组数据再跑一遍验证一下,这个是后话。 通过TAIR号在TAIR官网查找蛋白序列,创建fa文件后进行本地blastp比对 12blastp -query PIF4_pep.fna -out PIF4_blastp_nr_Kc.out -db nr_Kc -outfmt 6 -evalue 1e-5 -num_threads 4blastp -query HMR_pep.fna -out HMR_blastp_nr_Kc.out -db nr_Kc -outfmt 6 -evalue 1e-5 -num_threads 4 注意下结果文件名写清楚什么基因,用的什么方法比对,比对的什么库。这个时候再查看各自的结果文件,发现有比对结果,再回到非冗余转录本文件找对应的cds序列。操作过程都一样,这里不再赘述了。 3 总结找三条同源基因花了一整天的时间,主要原因还是对同源序列了解不够深刻。 同源就是有共同的进化祖先,序列相似性搜索可以通过检测过高的相似性来识别同源蛋白质或基因:当两个序列的相似性超过偶然的预期时,我们推断这两个序列存在同源性。 当观察到过高的相似性时,这两个序列不是独立出现的,它们起源于一个共同的祖先。 通过算法进行序列对库比对的工具,比如blast等,是通过过高相似性来减少假阳性的结果。所以通过算法在统计学上找不到库里显著的匹配项,不代表这个物种中一定没有同源基因。 从这次blastn和blastp比对结果来看,核酸序列比对可能更不容易找到同源序列。其实也好理解,生物在进化的几亿年时间里,很难保证不同物种有高相似性的核酸序列。同个氨基酸有不同密码子(简并性),也能证明蛋白质一级结构才是对生物影响最大的,蛋白质序列相同,就会有相似结构和功能。因此,蛋白质序列也就是氨基酸序列,对相似性的搜索比核酸序列要敏感的多。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"blast","slug":"blast","permalink":"http://www.shelven.com/tags/blast/"}]},{"title":"0基础学习基因组三代测序组装(4)——初步组装二代数据","slug":"0基础学习基因组三代测序组装(4)——初步组装二代数据","date":"2022-07-03T11:28:52.000Z","updated":"2022-12-03T16:12:27.000Z","comments":true,"path":"2022/07/03/a.html","link":"","permalink":"http://www.shelven.com/2022/07/03/a.html","excerpt":"经过前面的全基因组特征调查(survey)后,我们发现这是一个复杂基因组,杂合度较高,可以以二代+三代测序技术相结合的策略进行全基因组组装,还可以以Hi-C(高通量染色体捕获技术,High-through chromosome conformation capture)技术进行辅助组装。这里我用华大开发的二代测序组装工具SOAPdenovo,用二代测序数据对进行初步基因组组装。","text":"经过前面的全基因组特征调查(survey)后,我们发现这是一个复杂基因组,杂合度较高,可以以二代+三代测序技术相结合的策略进行全基因组组装,还可以以Hi-C(高通量染色体捕获技术,High-through chromosome conformation capture)技术进行辅助组装。这里我用华大开发的二代测序组装工具SOAPdenovo,用二代测序数据对进行初步基因组组装。 1 安装SOAPdenovo 2.0github上这个软件的版本是2.0,网址点击这里 软件下载安装过程非常顺利,如果有报错无法解决的话可以在Issue里向作者反馈。 1234# 集群如果无法登录github,下载源码包,通过xftp传到集群tar -zxvf SOAPdenovo2cd SOAPdenovo2-r242make 编译之后可以看到有如下几个文件 SOAPdenovo-127mer和SOAPdenovo-63mer是用于组装的两个程序,分别代表支持的最大k-mer为127和63,用法上是完全相同的。 example.config是配置文件,组装之前我们要设置其中的参数内容;README.md是帮助文件,详细记录了各项参数的作用和设置方法。这个后面会讲到。 2 kmergenie计算最佳k值现在组装基因组的算法主要有三种:De Bruijn graph,Overlap-Layout-Consensus和String Graph。SOAPdenovo软件组装基因组用的是De Bruijn graph算法,简单理解是通过将reads打断成k-mer后,利用k-mer之间的重复部分构建图,得到最优化路径从而拼接contig。要具体了解什么是De Bruijn graph,可以参考这一篇博文。 不同k-mer值构建的De Bruijn graph不一样,会导致组装质量的差异,因此我们需要选择一个最佳的组装k-mer大小(尽管可以用默认值23直接组装,但是效果不一定是最好的)。 kmergenie软件和之前的Jellyfish类似,都可以用于统计k-mer数量,kmergenie最大优点是可以对预设的多个k-mer进行分析,找到最佳的k-mer。点击这里进入Kmergenie官网,下载最新版本的软件。 注意下这个软件安装需要python > 2.7,并且需要安装R和zlib。 12345678tar -zxvf kmergenie-1.7051.tar.gzcd kmergenie-1.7051makepython setup.py install --user # 安装到用户环境中,不报错说明可以使用vim file.txt # 将两个fq文件路径写进去,一行一个/public/home/wlxie/biosoft/kmergenie-1.7051/kmergenie file.txt -o ./kmergenie_res -l 15 -k 65 -s 5 -t 30 --diploid # 运行kmergenie -o # 输出文件位置和名称 -l # 设定的最小k值 -k # 设定的最大k值 -s # 最小k值到最大k值,每次增加的间隔(根据需要设定间隔大小) -t # 运行的线程(CPU核)数 --diploid # 二倍体模式,前面我们已经用jellyfish确认过这是个复杂基因组。默认是单倍体模式 其原理就是设置不同k值进行基因组大小预估,将组装的基因组最大的k值作为最佳k值。 最终会给出kmergenie_res为前缀的一系列报告,生成的.histo文件还可以用来上一篇笔记中的GenomeScope分析,这里我们只需要看总结的html文件。 确定组装的最佳k值为51 3 SOAPdenovo2组装contigs/scaffolds复制一份example.config配置文件,重命名为run_config,修改部分参数 123456789101112131415161718192021222324252627282930#maximal read length#全局配置参数,只要高于这个参数的序列都会被截取到这个长度max_rd_len=150#文库配置以[LIB]开头[LIB]#average insert size#文库插入片段的平均长度,在实际设置时,可以参考文库size分布图,取峰值(默认200)avg_ins=200#if sequence needs to be reversed#是否需要将序列反向互补,对于pair-end数据,不需要反向互补,设置为0;对于mate-pair数据,需要反向互补,设置为1reverse_seq=0#in which part(s) the reads are used#1表示只组装contig,2表示只组装scaffold,3表示同时组装contig和scaffold,4表示只补gapasm_flags=3#use only first 100 bps of each read#序列长度阈值,作用和max_rd_len相同,大于该长度的序列会被切除到该长度rd_len_cutoff=150#in which order the reads are used while scaffolding#设置不同文库数据的优先级顺序,取值范围为整数,rank值相同的多个文库,在组装scaffold时,会同时使用。rank=1#cutoff of pair number for a reliable connection (at least 3 for short insert size)#contig或者scaffold之前的最小overlap个数,对于pair-end数据,默认值为3;对于mate-paird数据,默认值为5pair_num_cutoff=3#minimum aligned length to contigs for a reliable read location (at least 32 for short insert size)#比对长度的最小阈值,对于pair-end数据,默认值为32;对于mate-pair数据,默认值为35map_len=32#a pair of fastq file, read 1 file should always be followed by read 2 file#过滤后的双端测序数据文件路径,q为fastq格式,f为fasta格式,b为bam格式q1=/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Second-generation_sequencing/20211106-BaiYiHuiNeng01/01.rawFq/00.mergeRawFq/1/clean_data/1_r aw_1_val_1.fqq2=/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Second-generation_sequencing/20211106-BaiYiHuiNeng01/01.rawFq/00.mergeRawFq/1/clean_data/1_r aw_2_val_2.fq SOAPdenovo有6个子命令pregraph、sparse_pregraph、contig、map、scaff和all,前5个命令对应5个组装步骤,第一和第二是两种不同构图方式,all命令一次执行所有步骤,用all命令比较省事儿。 SOAPdenovo命令还有一些参数用于调整,参数参考 123456789101112131415-s # 配置文件-o # 输出文件的前缀-K # 输入的K-mer值大小,默认值23-p # 程序运行时设定的线程数,默认值8-R # 利用read鉴别短的重复序列,默认值不进行此操作-d # 去除频数不大于该值的k-mer,默认值为0-D # 去除频数不大于该值的由k-mer连接的边,默认值为1,即该边上每个点的频数都小于等于1时才去除-M # 连接contig时合并相似序列的等级,默认值为1,最大值3。-F # 利用read对scaffold中的gap进行填补,默认不执行-u # 构建scaffold前不屏蔽高覆盖度的contig,这里高频率覆盖度指平均contig覆盖深度的2倍。默认屏蔽-G # 估计gap的大小和实际补gap的大小的差异,默认值为50bp。-L # 用于构建scaffold的contig的最短长度,默认为:Kmer参数值 ×2-k # map步骤中kmer的大小,默认是和K一样的kmer大小-N # 基因组大小-V # 输出可视化的组装信息 运行组装命令 1/public/home/wlxie/biosoft/SOAPdenovo2-r242/SOAPdenovo-63mer all -s /public/home/wlxie/biosoft/SOAPdenovo2-r242/run_config -K 51 -R -V -o A_venetum -p 30 程序运行了3个小时,结束后生成了以下文件 4 组装结果解读组装结果文件其实只有两个,分别以**.contig结尾和.scafseq结尾**。因为我是在集群上运行的,slurm-11168.out是集群的输出日志文件,记录了详细的组装过程和结果。 最终得到935861个contigs,总长度295302126 bp,平均长度315 bp,最长的长度38673 bp,contig N50是532 bp,contig N90是103 bp;scaffold个数77918,总长度192992858 bp,平均长度2476 bp,最长的长度108587 bp,scaffold N50是3385 bp,scaffold N90是130 bp。(可以做一个统计表) 从组装的contig覆盖深度和数量还可以做一个柱状图,理论上来说是和前面k-mer分布图呈现一样的趋势,也就是一个主峰和一个杂峰,两个图相互印证目标基因组是个复杂基因组。 其他的结果文件在github上有解释,我先直接复制过来,以后用到再翻译翻译…… 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950511. Output files from the command "pregraph"a. *.kmerFreq Each row shows the number of Kmers with a frequency equals the row number. Note that those peaks of frequencies which are the integral multiple of 63 are due to the data structure.b. *.edge Each record gives the information of an edge in the pre-graph: length, Kmers on both ends, average kmer coverage, whether it's reverse-complementarily identical and the sequence.c. *.markOnEdge & *.path These two files are for using reads to solve small repeats.e. *.preArc Connections between edges which are established by the read paths.f. *.vertex Kmers at the ends of edges.g. *.preGraphBasic Some basic information about the pre-graph: number of vertex, K value, number of edges, maximum read length etc. 2. Output files from the command "contig"a. *.contig Contig information: corresponding edge index, length, kmer coverage, whether it's tip and the sequence. Either a contig or its reverse complementry counterpart is included. Each reverse complementary contig index is indicated in the *.ContigIndex file.b. *.Arc Arcs coming out of each edge and their corresponding coverage by readsc. *.updated.edge Some information for each edge in graph: length, Kmers at both ends, index difference between the reverse-complementary edge and this one.d. *.ContigIndex Each record gives information about each contig in the *.contig: it's edge index, length, the index difference between its reverse-complementary counterpart and itself. 3. Output files from the command "map"a. *.peGrads Information for each clone library: insert-size, read index upper bound, rank and pair number cutoff for a reliable link. This file can be revised manually for scaffolding tuning.b. *.readOnContig Reads' locations on contigs. Here contigs are referred by their edge index. Howerver about half of them are not listed in the *.contig file for their reverse-complementary counterparts are included already.c. *.readInGap This file includes reads that could be located in gaps between contigs. This information will be used to close gaps in scaffolds if "-F" is set. 4. Output files from the command "scaff"a. *.newContigIndex Contigs are sorted according their length before scaffolding. Their new index are listed in this file. This is useful if one wants to corresponds contigs in *.contig with those in *.links.b. *.links Links between contigs which are established by read pairs. New index are used.c. *.scaf_gap Contigs in gaps found by contig graph outputted by the contiging procedure. Here new index are used.d. *.scaf Contigs for each scaffold: contig index (concordant to index in *.contig), approximate start position on scaffold, orientation, contig length, and its links to others contigs.e. *.gapSeq Gap sequences between contigs.f. *.scafSeq Sequences of each scaffolds.g. *.contigPosInscaff Contigs' positions in each scaffold.h. *.bubbleInScaff Contigs that form bubble structures in scaffolds. Every two contigs form a bubble and the contig with higher coverage will be kept in scaffold.i. *.scafStatistics Statistic information of final scaffold and contig.","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"kmergenie","slug":"kmergenie","permalink":"http://www.shelven.com/tags/kmergenie/"},{"name":"SOAPdenovo2","slug":"SOAPdenovo2","permalink":"http://www.shelven.com/tags/SOAPdenovo2/"}]},{"title":"0基础学习基因组三代测序组装(3)——全基因组Survey","slug":"0基础学习基因组三代测序组装(3)——全基因组Survey","date":"2022-07-02T07:08:17.000Z","updated":"2022-12-03T16:13:20.000Z","comments":true,"path":"2022/07/02/a.html","link":"","permalink":"http://www.shelven.com/2022/07/02/a.html","excerpt":"之前说到如何对三代测序数据做污染评估,取随机序列做blastn比对nt库,确定物种分布情况。实际blast比对还要考虑比对的序列长度和ONT本身数据错误率,以及结合GC-depth确定是否有污染。基因组三代测序数据组装之前,我们还要做一个全基因组survey。主要是为了减少盲目性,先做低深度的基因组分析,也是初步了解物种基因组特征的有效方法,比如评估基因组大小和杂合情况,为后续全基因组de novo组装策略指定提供指导。","text":"之前说到如何对三代测序数据做污染评估,取随机序列做blastn比对nt库,确定物种分布情况。实际blast比对还要考虑比对的序列长度和ONT本身数据错误率,以及结合GC-depth确定是否有污染。基因组三代测序数据组装之前,我们还要做一个全基因组survey。主要是为了减少盲目性,先做低深度的基因组分析,也是初步了解物种基因组特征的有效方法,比如评估基因组大小和杂合情况,为后续全基因组de novo组装策略指定提供指导。 基因组复杂程度的经验性标准: 简单基因组: 单倍体;或纯合二倍体;或杂合度低于0.5%, 且重复序列低于50%, 且GC含量在35%-65%的二倍体。 复杂基因组: 杂合度在0.5%~1.2%之间,或重复序列高于50%,或GC含量异常(<35%或>65%)的二倍体,或者多倍体。复杂基因组可以采用“2+3”即二代和三代测序技术相结合,加之Hi-C辅助组装的组装策略。 高复杂基因组: 杂合度>1.2%;或重复序列占比大于65%。 有条件的话,也可以用流式细胞仪对基因组大小做个预估。我这里只有二代基因组测序数据,因此用基因组二代测序数据做全基因组survey。当然,这里要注意一点,做全基因组survey的样本和后续de novo组装的样本要来自同一个个体,避免个体间基因组特征的差异。 1 原始数据质控因为是对二代测序数据进行分析,质控的过程本质上和之前处理转录组二代数据一样,这里只提下过程和结果。 1.1 fastqc生成质控报告1fastqc *.fq.gz -o ./ 二代测序是双端测序结果,我这里只截图了部分qc报告,可以看出GC含量比较稳定,测序质量也比较高。 1.2 trim-galore数据过滤报告中的结果虽然好,但是还是需要过滤一遍,把末端接头adapter序列过滤掉。 1trim_galore -q 25 -phred33 -length 100 -stringency 1 -paired -o clean_data 1_raw_1.fq.gz 1_raw_2.fq.gz 参数在这篇博客 转录组数据分析笔记(1)——如何用fastqc和trim-galore做测序数据质控 有提到,这里不再赘述。 看下report文件,过滤了Q值25以下的reads和adapter序列 2 k-mer分析先说一下k-mer的概念:k-mer在这里指将reads迭代拆分成包含k个碱基的序列(类似blast中的word length,蛋白质是3,核酸是11),我们后面要分析的基因组特征都是基于k-mer分布基础上进行的。 基因组大小可以通过总 (K-mer 数量)/(K-mer 期望测序深度)来估计 k-mer分布曲线的主峰所在横坐标可以作为期望的测序深度 测序覆盖均匀、不存在测序误差和基因组重复序列的理论条件下,K-mer分布曲线会符合泊松分布 单倍体或纯合基因组的 K-mer 分布曲线只有一个主峰 杂合二倍体基因组的 K-mer 分布曲线有两个峰, 分别为杂合峰(主峰1/2处)和纯合峰(主峰),前者深度只有后者的一半 重复序列含量较高时会在主峰后面形成一个重复峰(主峰的2倍处)或者形成拖尾 一般选择17-mer评估基因组大小,因为ATCG组成长度为17的核酸序列,理论上有4的17次方种可能,足以覆盖一般的正常基因组。为了避免回文序列,K-mer分析选择K长度均为奇数。 2.1 安装jellyfish根据上面说的k-mer概念,可以理解k-mer分析是非常耗计算资源的。我们要自己用脚本实现的话,需要将十几个G的reads分割成不同长度片段,再统计出现的次数,耗时而且麻烦。jellyfish是一款统计DNA序列中Kmer的分布的软件,它运行速度快,内存消耗低,支持并行,也是用的最多的统计k-mer的软件。 重点是可以通过conda直接安装……最好不要用conda安装,我之前运行了1天没出结果也没报错(一度怀疑我的参数设置是不是有问题),百思不得其解。后来从github上重新下载,编译和安装之后,不到10分钟就跑出结果了…我不知道两种安装方式有什么区别,这里就记录下自己踩的坑。 因为jellyfish不支持.gz的压缩文件,所以之前用tram galore过滤后得到的clean reads需要用gunzip命令解压。 1conda install -c bioconda jellyfish # 可以用conda安装,我运行的时候出了问题,暂未解决,不推荐 点击这里进入jellyfish的github下载地址 我们用本地安装的方式,先下载tar.gz的源码包,tar -zxvf解压后进入jellyfish-2.3.0文件夹。 我是集群登录的,下面讲的步骤都是在集群上操作(非root账户) 12345678# 第一步检测。本质上是一个shell脚本,根据系统环境产生合适的makefile文件或者C的头文件(.h结尾的文件),非root账户下--prefix后面接上自己账户的绝对路径。./configure --prefix=/public/home/wlxie# 第二步编译。对源代码包进行编译,如果有错误自己看是否有依赖库的缺失,主要是这个问题。make# 第三步安装。如果前面没有指定自己账户的路径,这一步是会报错没有权限的(用户不能向系统目录写入文件)。make install# 第四步自检。make check make和make check这两步都会因为动态链接库命名不同,导致报错无法找到动态库;以及我在检测通过之后,用集群运行程序仍然出现了动态库的某个模块无法调用的情况。这里统一说下解决方法。 前面configure会在我们的家目录下生成bin、lib和share目录,这里比较重要的是bin和lib目录。我们运行的命令在bin目录里,对应要改环境变量PATH;而需要调用的动态库是在lib目录下,对应要改环境变量LD_LIBRARY_PATH。家目录下的.bashrc文件加入以下内容 12export PATH="/public/home/wlxie/bin:$PATH"export LD_LIBRARY_PATH="/public/home/wlxie/lib:$LD_LIBRARY_PATH" 添加之后保存退出,并且source ~/.bashrc刷新一下系统环境变量。 我碰到的报错是libcrypto.so.1.0.0和libstdc++.so.6这两个动态库找不到,但是locate命令查看这两个动态库,在系统目录/lib64/下都能找到文件,因此将这两个动态库文件直接复制到家目录的lib文件夹,问题就全部解决了。 如果libstdc++.so.6报错某版本的文件不存在,可以先到动态库目录下,运行strings命令查看动态库中是否有对应的文件: 1strings /lib64/libstdc++.so.6 | grep CXXABI # 比如找不到GLIBCXX_3.4.26,看看动态库中是否存在这个版本的文件,如果不存在,更新动态库;如果存在但是找不到,建议直接拷贝到自己的lib目录下 make check之后会生成一个日志文件test-suite.log,没有fail的项目说明软件安装成功,没有问题。 2.2 k-mer频数分布123456# k-mer计数jellyfish count -m 17 -s 300M -t 50 -C -o 17-mer.jf ./1_raw_1_val_1.fq ./1_raw_2_val_2.fq# k-mer频数统计jellyfish histo -t 4 17-mer.jf > 17-mer.histo# 统计总k-mer数和特征k-mer数等jellyfish stats 17-mer.jf -o counts_stats.txt 记录一下各个参数的意义: -m # k-mer长度设置为17bp,进行计数 -s # 存储用的hash表大小,说实话我没看懂什么意思,基因组估计有多大就用多大就是了,单位是M或者G -t # 使用的线程数,也就是cpu核数 -C # 大写的C,对正负链reads都进行统计,双端测序一定要加这个参数 -o # 结果文件的前缀名,结果文件是一个二进制文件 正常来说,10分钟就能跑完程序并给出k-mer计数结果文件。我用conda安装的jellyfish同样条件运行了20个小时没有结束……而且还不报错!第一次运行这个软件,没有人参考和交流,百度到的教程都是抄来抄去的也没有人说明时间的问题……以后还是去官网安装生信软件了,虽然麻烦一点但是靠谱…… 这个软件的帮助文档在/jellyfish-2.3.0/doc目录下,所有功能和参数都有英文详解。 k-mer频数统计是在计数结果文件上进一步统计各个k-mer出现的次数,频数统计结果文件17-mer.histo将k-mer从1统计到10000,最后一行是10001以后对应的总频次。counts_stats.txt是总的统计结果,包括k-mer总数(Total),特异的k-mer数目(Distinct)只出现过一次的k-mer数量(Unique),频数最高的k-mer数量(Max_count)四项。 有了频数统计结果文件17-mer.histo就可以用R作图了,以下R作图代码来自于CSDN博主 生信技工 1234567kmer <- read.table('17-mer.histo')kmer <- subset(kmer, V1 >=5 & V1 <=500) # 只取5-500bp长度的k-mer统计频次Frequency <- kmer$V1Number <- kmer$V2png('kmer_plot.png')plot(Frequency, Number, type = 'l', col = 'blue')dev.off() # 保存png k-mer分布图如下,当然这只是一个简略图,上面R作图代码还有很多细节可以补充 2.3 基因组大小、重复率、杂合率估算横坐标表示k-mer深度,纵坐标为k-mer数量,可以看得出来测序的样本是个杂合二倍体。主峰坐标(116,2584902),杂合峰坐标(57,1188461),也就是说k-mer期望深度为116;k-mer总数为34655456060;主峰2倍深度也就是232之后的k-mer为重复序列k-mer,总数可以通过导出17-mer.histo文件进行统计(改成csv格式直接两步算出),共16361378388 k-mer分布曲线中无异常峰,说明二代测序提取的DNA纯度较高,没有被污染 根据(K-mer 数量)/(K-mer 期望测序深度)估算基因组大小为298M。去除深度小于5的错误k-mer,估算基因组大小为292M. 根据(重复序列的k-mer总数)/(K-mer 期望测序深度)估计重复序列大小为141M,即重复率48.29% 单拷贝序列大小U=292-141=151M,要计算杂合率,需要统计非重复k-mer的总数,也就是计算杂合峰面积,建议还是用软件或者在线工具比如genomescope2.0 jellyfish + GenomeScope是一套应用非常广泛的基因组survey方法,GenomeScope2.0适合用于分析二倍体生物。 上面是GenomeScope2.0网页版界面,只要我们提供jellyfish生成的.histo结果文件,设置参数就行 k-mer length # k-mer长度 Ploidy # 染色体倍性 Max k-mer coverage # 默认-1,即不限制最大k-mer深度,我这里限制了10000 Average k-mer coverage for polyploid genome # 默认-1,不进行筛选 提交后几分钟就生成了可用于发表的图和报告 可以看到估计的基因组大小是200M,杂合率0.865%,杂合峰覆盖度(深度)58.3。下面说下这个图如何解读: 蓝色区域是实际观测值 黑色拟合线是去除错误(errors)后剩下的k-mer分布,认为是正确的数据并以此评估基因组大小 黄色拟合线是非重复区域的k-mer分布(理想情况) 橙色拟合线区域是低深度的错误k-mer,认为是测序错误引入的 黑色虚线是k-mer的几个峰值 之所以估计的基因组大小比之前自己估计的要小,是因为去除error的标准不同,我之前只是简单去除了k-mer深度1-4的错误序列,这里是构建模型选择的错误序列,更准确一些。 网页版最后的results里还有总的统计结果,可以很方便地计算重复率,一眼就能看明白这里就不赘述了。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"jellyfish","slug":"jellyfish","permalink":"http://www.shelven.com/tags/jellyfish/"},{"name":"GenomeScope2.0","slug":"GenomeScope2-0","permalink":"http://www.shelven.com/tags/GenomeScope2-0/"}]},{"title":"0基础学习基因组三代测序组装(2)——数据污染评估","slug":"0基础学习基因组三代测序组装(2)——数据污染评估","date":"2022-06-19T17:17:30.000Z","updated":"2023-01-13T11:02:00.000Z","comments":true,"path":"2022/06/20/a.html","link":"","permalink":"http://www.shelven.com/2022/06/20/a.html","excerpt":"做完基因组三代测序数据质控之后,我们把所有reads的Q值控制在7以上,每个read的长度在1000bp以上。我们不能明确自己的测序数据是否被其他物种污染,这个时候就要用balst比对的方法确定测序数据是否被污染,以及污染的来源。","text":"做完基因组三代测序数据质控之后,我们把所有reads的Q值控制在7以上,每个read的长度在1000bp以上。我们不能明确自己的测序数据是否被其他物种污染,这个时候就要用balst比对的方法确定测序数据是否被污染,以及污染的来源。 1 下载balst+工具和数据库在之前的一篇博客中,我详细介绍了如何本地安装NCBI的blast+工具,以及下载nr/nt库,建立本地的数据库。详情点击这里。 在做数据污染评估的时候,我们还需要知道blast最佳结果对应的物种名,因此还需要下载分类数据库的以下两个子库: 12345678# ascp工具下载大数据,wget命令下载小文件(md5校验文件)ascp -QT -i /public/home/wlxie/miniconda3/envs/biosoft/etc/asperaweb_id_dsa.openssh -k1 -l 500m anonftp@ftp.ncbi.nlm.nih.gov:/pub/taxonomy/accession2taxid/nucl_gb.accession2taxid.gz ./wget https://ftp.ncbi.nlm.nih.gov/pub/taxonomy/accession2taxid/nucl_gb.accession2taxid.gz.md5ascp -QT -i /public/home/wlxie/miniconda3/envs/biosoft/etc/asperaweb_id_dsa.openssh -k1 -l 500m anonftp@ftp.ncbi.nlm.nih.gov:/pub/taxonomy/taxdump.tar.gz ./wget https://ftp.ncbi.nlm.nih.gov/pub/taxonomy/taxdump.tar.gz.md5 md5文件校验完成之后,两个数据库分别解压。注意.gz文件用gunzip,.tar.gz文件用tar -zxvf 看看这两个数据库长什么样: 第一张图片是nucl_gb.accession2taxid,我们需要用到第二列版本信息和第三列的taxid。 第二张图片是names.dmp,我们需要用到有taxid,学名和scientific name字符串的行。 这两个数据库怎么使用后面会详细说明。分析思路来自于CSDN的博主风风是超人,遗憾的是从17年开始,NCBI不再提供gi号与blastn结果的关联,博主的本地数据库可能版本比较早,采用的是gi号分析。 我将后续的代码做了修改,下载的也都是最新的数据库。总的逻辑是利用blast结果的version号,得到nucl_gb.accession2taxid数据库中的taxid号,最后通过names.dmp中的taxid号得到学名。代码方面做了少许优化,对集群服务器可能更友好一点? 2 fq文件处理和blast质控后的数据fq文件是“@”开头的,我们要改成fa格式也就是“>”开头。取前10000条序列,每个序列有4行,只取第一行标题和第二行序列。 12345# NR表示当前行,判断除以4的余数,余数1为标题行,只输出第一个元素即reads id;余数2则为序列行,输出所有元素也就是整条序列。最后替换@符号,文件名为test.fazcat clean_filter.fq.gz | head -n 40000 | awk '{if(NR%4==1){print $1}else if(NR%4==2){print $0}}' | sed 's/@/>/g' >test.fa# 批量blast程序blastn -query test.fa -out test_blastn_nt.xml -db nt -outfmt 5 -evalue 1e-5 -num_threads 20 -max_target_seqs 1 批量blast程序注意下我们输出的格式为xml格式,也就是- outfmt 5。为什么要用xml格式,因为xml格式能给出的信息最全,我们需要知道输出的版本号 evalue值根据需要设定,这里我设置1e-5 最大匹配数量注意下设置1,我们只需要知道和哪个物种相似度最高,一个输出结果就足够了(虽然设置1会有警告)。 看下blast生成的test_blastn_nt.xml这个结果文件: 虽然是第一次接触xml格式,但是感觉非常熟悉!之前做的一个微博爬虫小程序就是扒了一个类似的html格式的文件……xml格式也挺容易解析的,可以看到比对信息以标签<Iteration>开始,以</Iteration>标签结束,<Hit>标签开始表示的是比对上的结果(因为我设置了最大比对序列数量是1,所以<Hit_num>只有1);<Hsp>标签表示某一块的比对结果(同一条序列,若干片段比对上),因此<Hsp_num>标签的数量可能不止一个。 当然,这些都可以不用关心,分析需要的信息我用红框标了出来。比较重要的是<Hit_def>标签,里面的字符串是空格隔开的,第一个元素是我们需要的物种版本号。 3 XML文件解析前面说了解析的思路,以下是代码的实现。因为用的python语言写的程序,我的建议是在vscode一类的编程软件中写这些代码,如果有错误可以及时调试。 123456789101112131415161718192021222324252627282930313233import sysimport refrom collections import defaultdict# 可以不写,我是为了确保导入父目录的模块不出错sys.path.append("./")xmlfile = open("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/test_blastn_nt.xml","r")outfile = open("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/filted_accession_version.txt","w")# 定义一个空的字典,提取有queryID和subjectID的行dict1 = defaultdict(list)for lines in xmlfile: line = lines.strip() read_id = re.match('<Iteration_query-def>.*</Iteration_query-def>',line) Hit_def = re.match('<Hit_def>.*</Hit_def>',line) # 解析queryID if read_id != None: read_id = read_id.group() read_id = read_id.split("<")[1].split(">")[1] key=read_id # 字典key值和value值赋值 elif Hit_def !=None: Hit_def = Hit_def.group() Hit_def = Hit_def.split("<")[1].split(">")[1] dict1[key].append(Hit_def)# 写入文件,制表符分割for key in dict1: outfile.write(key + "\\t" + "\\t".join(dict1[key])+"\\n") 看下运行结束后解析得到的文件: 一共是两列,第一列是reads的queryID,第二列subjectID就是比对上的序列信息。可以看到第二列可以以空格为分隔符,提取第一个元素也就是物种版本号,后面会说。 为什么要把物种版本号提出来而不直接用这段内容里的物种名呢?因为不同的物种名字段数量和位置不一样,无法用统一的命令直接提取,精确的版本号可以对应唯一一个taxid,从而被精准地注释上物种学名。 4 匹配物种学名这里需要注意一个问题,blast用的nt库还有物种分类用到的两个数据库,他们的更新时间是不一致的。也就是说,物种版本号不一定能完全匹配上taxid,而taxid也不一定能匹配上学名。 而python语言写的程序,用到字典类型数据的时候,如果没有对应的key值匹配是会报错的,不会继续执行下去。一会儿解释,代码如下: 12345678910111213141516171819202122232425262728293031323334353637383940import sysimport gcsys.path.append("./")accession = open("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/filted_accession_version.txt","r")accession2taxid = open("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/nucl_gb.accession2taxid","r")taxid2name = open("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/names.dmp","r")final_res = open("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/final_res.txt","w")# 从names.dmp提取taxid和学名,匹配有scientific name的行taxid_name_dict = {} for lines in taxid2name: if "scientific name" in lines: line = lines.strip().split("|") taxid = line[0].strip() name = line[1].strip() taxid_name_dict[taxid] = name# 从nucl_gb.accession2taxid提取taxid和版本号accession_taxid_dict = {} for lines in accession2taxid: line = lines.strip().split("\\t") TAXID = line[2] VERSION = line[1] accession_taxid_dict[VERSION] = TAXID# 添加两个判断条件,版本号匹配不上taxid和taxid匹配不上学名的情况。gc.collect()释放内存。for lines in accession: line = lines.strip().split("\\t") version = line[1].split()[0] if version in accession_taxid_dict: taxid = accession_taxid_dict[version] if taxid in taxid_name_dict: final_res.write("\\t".join(line)+"\\t"+taxid_name_dict[taxid]+"\\n") else: final_res.write("\\t".join(line)+"\\t"+"INVALID TAXID"+"\\n") else: final_res.write("\\t".join(line)+"\\t"+"INVALID ACCESSION VERSION"+"\\n") gc.collect() 如果不加最后两个判断条件,程序会在报错的那行read序列终止。 通过比较两个输出结果文件行数是否一致来判断匹配是否完全。 两个文件输出结果一致说明匹配完成,为什么这里是9338而不是我们一开始blast的10000条序列呢?那是因为有662条序列balst结果的E值大于1e-5,没有在nt中比对上合适的序列 5 输出物种注释分布结果到这一步就有很多种处理方法了,可以把结果文件直接用excel打开,统计reads在nt库的分布情况和比对上的物种分布。也可以直接写个python脚本做个数据统计。 统计前我们先检查一下是否存在上一步匹配失败的reads。 123# 统计匹配失败的readscat final_res.txt | grep "INVALID TAXID"cat final_res.txt | grep "INVALID ACCESSION VERSION" 提示有8条reads的物种版本号比对不上taxid,且都是Pyrus x bretschneideri这个物种,说明这个物种还未在nucl_gb.accession2taxid这个NCBI官方数据库中更新。在结果文件中将其替换掉。 12# sed命令在原文件进行全局替换sed -i 's/INVALID ACCESSION VERSION/Pyrus x bretschneideri/g' final_res.txt 修改完成,检查无误后,用以下python脚本统计物种注释分布: 123456789101112131415161718192021222324252627from collections import Counter # 引入counter模块import syssys.path.append("./")name_file = open("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/final_res.txt","r")res_stastics = open("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/stastics.txt","w")name_list = []for lines in name_file: line = lines.strip().split("\\t") name = line[-1] # 取最后一列 name_list.append(name)# Counter()函数统计词频count_result = Counter(name_list)count_list = list(count_result.items()) # 注意需要创建一个listcount_list.append(('Unmap',662)) # 注意手动添加blast失败的序列条数到list中count_list.sort(key=lambda x:x[1],reverse=True) # 以第二维数据值,即统计的物种学名出现次数排序res_stastics.write("Name\\tHit_reads\\tpercentage\\n")for i in count_list: name = i[0] number = i[1] reads_num = 10000 percentage ="%.2f%%"%(100*float(number)/float(reads_num)) # 浮点两位小数的百分比 res_stastics.write(name+"\\t"+str(number)+"\\t"+str(percentage)+"\\n") 需要注意手动添加blast失败的序列条数,方便最后一起统计。打开生成的stastics.txt文件: 这个数据是制表符分割的,可以用excel做一个分布统计表,或者用R做一个柱状图,底下展示结果 可以看到,10000条序列比对结果占比最高的前两条序列(橘黄色的)是细菌的核酸序列,总数达到3437条。992条序列比对上罗布麻,662条序列未比对上nt库。可以认为这个序测序数据被细菌污染,可以和测序公司battle要求重新测一遍了…… 6 补充说明2022/12/4 更新秉着科学严谨的态度,再更新一些内容查漏补缺。 质控过滤后的reads有183万条,而我只取了前1万条。考虑到测序开头的低质量reads可能会对分析结果产生干扰(比如开头的电信号不稳定),我写了个python脚本对过滤后的数据随机取10000条reads,这样就只有随机误差影响分析结果了。 在第2步fq文件处理部分,为了python调用方便,先解压clean_filter.fq.gz文件 1gunzip clean_filter.fq.gz 读取解压后的文件需要49G内存,我只能在集群上处理,接着运行如下python脚本 123456789101112131415import random # 调用random模块产生随机数import linecache # 调用linecache模块读入指定行import gcoutput_file = open("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/random_test.fa","w")reads_list = list(range(1,1834926)) # 共有1834925条readsline = random.sample(reads_list,10000)for i in line: text1 = linecache.getline("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/clean_filter.fq",4*i-3) text2 = linecache.getline("/public/home/wlxie/luobuma/luobuma/sample_1_rawdata/Third_generation_sequencing/clean_filter.fq",4*i-2) query_id = text1.split()[0] query_id_fa = query_id.replace("@", ">") output_file.write(query_id_fa + "\\n" + text2) gc.collect() 处理方式比之前多了几步,我运行了两次脚本,发现两次产生的文件大小都在135M左右,也就是随机取10000条reads产生的文件比取前10000条reads产生的文件大了40M。证明三代测序开头测得序列质量不太行(短序列不一定质量不好,但是质量不好的序列一定是短序列),拿到随机产生的10000条reads做blast,后续步骤都一样。 2023/1/13更新与测序公司技术人员沟通后,纠正一些误区: 三代测序数据是长读长片段,直接使用长读长序列进行blastn比对,能比对上数据库的概率会大很多(总有些区域能比对上一些同源序列,一条reads也可能比对上不同的物种)。 真核生物基因组含有大量的重复序列,对三代测序数据一般要进行随机打断成250-500bp大小,然后随机取一条进行blastn比对,如果这条序列刚好是重复序列片段,就会出现比对不上的结果(重复区域测序准确度也相对较低)。 因此,以上的方法更适合二代测序数据的污染评估,三代数据污染评估需要在代码上考虑实现随机打断成短片段(250-500bp),再进行blastn比对nt数据库。 总的来说,这种随机取测序read进行blastn来确定数据污染的方法,只是简单粗暴的看一下样本污染情况,如果污染比例低(通常小于1%),说明数据可以使用,还需要结合GC-depth做具体分析(只有一个红色区域说明数据无污染)。blastn比对这部分内容一般不会在文章中做展示。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"python","slug":"python","permalink":"http://www.shelven.com/tags/python/"},{"name":"blast+","slug":"blast","permalink":"http://www.shelven.com/tags/blast/"}]},{"title":"0基础学习基因组三代测序组装(1)——数据质控","slug":"0基础学习基因组三代测序组装(1)——数据质控","date":"2022-06-17T14:31:19.000Z","updated":"2022-12-03T16:14:50.000Z","comments":true,"path":"2022/06/17/b.html","link":"","permalink":"http://www.shelven.com/2022/06/17/b.html","excerpt":"最近拿到一个植物基因组的三代和二代测序数据,想通过以三代测序数据为主,二代测序数据为辅的方式学习一下如何拼接组装一个基因组。但是三代测序数据刚到手就懵了,与之前学习的转录组分析不一样,三代测序返回的几个文件不是单纯的fq文件,于是我又开始恶补了一些三代测序的基础知识,开坑写个三代基因组测序组装的系列笔记~","text":"最近拿到一个植物基因组的三代和二代测序数据,想通过以三代测序数据为主,二代测序数据为辅的方式学习一下如何拼接组装一个基因组。但是三代测序数据刚到手就懵了,与之前学习的转录组分析不一样,三代测序返回的几个文件不是单纯的fq文件,于是我又开始恶补了一些三代测序的基础知识,开坑写个三代基因组测序组装的系列笔记~ 1 技术背景当第二代高通量测序技术进入成熟阶段后,读长过短、PCR扩增带来的偏向性等问题开始日益凸显;作为基因组学上新的转折点,以PacBio单分子实时测序技术及纳米孔单分子测序技术为首的第三代高通量测序技术(Third-generation Sequencing)开始进入科研应用,从单分子水平上对DNA分子的实时测序,成功解决了二代测序几大困扰:极端 GC含量区域覆盖度低、高度重复区域无法较好地拼装、大片段变异难以准确检测、不能直接检测碱基修饰等问题。 ONT(Oxford Nanopore Technologies)牛津纳米孔测序技术作为第三代单分子实时测序技术,其原理是基于高分子膜两侧电压和其中的蛋白质纳米孔,当单分子DNA从纳米孔通过时,会引起孔两侧电位差来实现信号检测, 而ATCG四种碱基的带电性质不一样,因此利用电信号的差异就能检测出通过的碱基类型,从而实现测序。 Nanopore商业化平台有三个:MinION、GridION及PromethION。本系列笔记的三代测序数据来源于PromethION测序平台测序的一个cell,PromethION测序仪拥有48个流动槽,每个流动槽拥有3000个纳米孔通道(总计144000个),适用于大样本量的高通量快速测序。 2 数据质控basecalling在ONT的测序平台中,将通过纳米孔的DNA或RNA链产生的电位信号转化为相应的碱基序列的过程,称为basecalling。Nanopore测序的下机数据的原始数据格式为包含原始测序电信号的fast5格式,官方有提供工具Guppy进行basecalling,以mean_qscore_template的数值大于等于7为标准(也就是测序质量大于7的reads)得到原始测序数据,这样得到的basecalling数据为fastq格式(.fastq或者.fq结尾),所以我拿到的就是已经basecalling后的结果。 fast5: 原始电信号文件,以.fast5为文件结尾。此文件既有测序得到的序列信息,还有甲基化修饰信息(甲基化位点电信号会不一样)。 fastq: fast5文件转换而来,四行一个单位,序列和碱基质量一一对应。 basecalling的同时还可以一起拆分barcode条码序列,这里我没用到guppy这个软件,了解一下就行。经过basecalling后,文件会分为fail和pass两部分,pass部分就是满足Q值>7的序列(二代测序质控标准是Q20,这里的三代测序质控标准是Q7,准确性不及二代测序)。 还有一个summary.txt文件,这是一个测序汇总文件,结构如下: 第一眼看上去很乱,几个重要的列含义如下: filename_fastq fasq文件名 filename_fast5 fast5文件名 read_id 每条read对应的id号 run_id 这一次运行产生的id号,一个flowcell通常为一个run channel mux 该条read在哪个channel测的 start_time 这条read测序起始时间 duration 这条read测序经过时间 passes_filtering Q值大于7为TRUE否则为FALSE sequence_length_template read长度,三代测序数据过滤的指标之一 mean_qscore_template 非常重要的指标,每一个read的平均Q值 有关barcode的都是标签序列相关参数,因为不同样品接头会添加不同的标签序列,混测的时候根据标签序列与样品的对应关系来区分不同样品。 返回的数据是guppy处理过的,也就是raw reads,接下来质控的过程就需要自己动手了。 nanoplot质控先说明下为什么要用这个工具,三代测序的数据读长比二代测序长很多,而且每条序列的长度都是不一样的。不能用之前转录组数据分析中的fastq工具,会报错,因此使用nanoplot工具来生成质检报告,同样也是会生成各种html文件方便浏览结果。 先创建一个nanoplot专用的环境,下载nanoplot,之后的质控过程都在这个环境下进行。 12345678# 创建环境并下载nanoplotconda create -n nanoplot -y -c bioconda nanoplot# 激活环境. activate nanoplot# 生成质检报告。可以用pass.fq文件,也可以直接用summery.txt文件。-o参数后面是输出文件夹名称。NanoPlot --summary summary.txt --loglength -o summary-plots-log-transformedNanoPlot -t 4 --fastq pass.fq.gz --plots hex dot -o nanoplot_out# 详细参数设置可以在NanoPlot --help中查看 运行结束之后会生成summary-plots-log-transformed这个文件夹,我们可以用xftp工具查看里面的html结果文件,也可以挑取一些数据做数据质量统计表。 放一张原始测序数据读长分布图示意一下: 点击这里查看用summary.txt生成的质控报告 点击这里查看用pass.fq.gz生成的质控报告(推荐用这个) 两种方法生成的质控报告略微有点差别,因为summary.txt文件中记录了所有序列,可以看到有部分序列质量在Q5-Q7之间;而pass.fg.gz生成的质控报告中,所有序列的质量都在Q7及以上。后续以分析pass.fq.gz文件生成的质控报告为准,对这个文件序列的长度进行过滤。 如果不需要图,只需要知道有多少条reads、reads平均长度、N50、N90这些数据做表格的话,还有一个比较实用的perl脚本,怎么使用就不赘述了,源代码放底下参考。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354#/usr/bin/perl -wuse strict;use warnings;### *fastq.gz 数据统计 N50 N90 num_seqs sum_len min_len avg_len max_len ### usage: perl stat.fastq.gz.N50.N90.pl *.fastq.gzmy $fastq_gz = $ARGV[0];open(IN,"gzip -dc $fastq_gz|") or die ("can not open $fastq_gz\\n");open(OUT,">$fastq_gz.stat");print OUT "name num_seqs sum_len min_len avg_len max_len N50 N90\\n";print OUT "$fastq_gz\\t";my ($len,$total,$num_seqs,$min_len,$max_len)=(0,0,0,0,0);my @length_list;while(<IN>){ my $title = $_; my $seq = <IN>; my $add = <IN>; my $quality = <IN>; $seq =~ s/\\r|\\n|\\r\\n//mg; $len = length($seq); if($len>0){ $total += $len; push @length_list,$len; } if($min_len == 0){$min_len = $len;}elsif($min_len > $len){$min_len = $len;} if($max_len == 0){$max_len = $len;}elsif($max_len < $len){$max_len = $len;} $len=0; $num_seqs++;}my $avg_len = $total/$num_seqs;print OUT "$num_seqs\\t";print OUT "$total\\t";print OUT "$min_len\\t";print OUT "$avg_len\\t";print OUT "$max_len\\t";@length_list=sort{$b<=>$a} @length_list;my ($count,$half)=(0,0);for (my $j=0;$j<@length_list;$j++){ $count+=$length_list[$j]; if (($count>=$total/2)&&($half==0)){ print OUT "$length_list[$j]\\t"; $half=$length_list[$j] }elsif ($count>=$total*0.9){ print OUT "$length_list[$j]\\t\\n"; exit; }} filtlong过滤数据前面说过,二代测序是双端测序,三代测序是单端测序,两者过滤数据的要求不同。三代测序主要是过滤长度过短的序列和测序质量较低的序列。在basecalling中我们过滤了Q值小于7的序列,现在还要过滤read长度小于1000bp的序列。过滤后的序列可以直接用于后续的组装。 1234# 安装filtlong软件conda install -y filtlong# 设置序列最短为1000bp,压缩结果文件到新文件中filtlong --min_length 1000 pass.fq.gz | gzip > clean_filter.fq.gz 可以看到过滤了一部分数据,用过滤后的数据再跑一次NanoPlot 1NanoPlot -t 4 --fastq clean_filter.fq.gz --plots hex dot -o filt_nanoplot_out 测序数据读长分布如下,可以看到已经没有1kb以下的reads了: 点击这里查看过滤后的质控报告 至此质控过滤流程结束,我们可以做一个下机数据质控统计表: Type Bases(bp) Reads Number Reads mean length(bp) Reads N50 length(bp) Raw Reads 25,584,046,180.0 1,933,526.0 13,231.8 28,127.0 Filtered Reads 25,531,304,191.0 1,834,925.0 13,914.1 28,184.0","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"nanoplot","slug":"nanoplot","permalink":"http://www.shelven.com/tags/nanoplot/"},{"name":"filtlong","slug":"filtlong","permalink":"http://www.shelven.com/tags/filtlong/"}]},{"title":"NCBI的BLAST+工具本地安装,本地建库和BLAST比对","slug":"NCBI的BLAST+工具本地安装,本地建库和BLAST比对","date":"2022-06-16T18:33:56.000Z","updated":"2022-12-03T16:10:04.000Z","comments":true,"path":"2022/06/17/a.html","link":"","permalink":"http://www.shelven.com/2022/06/17/a.html","excerpt":"接触过生物学的小伙伴对NCBI在线BLAST网页一定不陌生,简单介绍一下这个网页的5种比对工具:blastn、blastp、blastx、tblastn和tblastx,以及如何进行本地建库和blast比对。","text":"接触过生物学的小伙伴对NCBI在线BLAST网页一定不陌生,简单介绍一下这个网页的5种比对工具:blastn、blastp、blastx、tblastn和tblastx,以及如何进行本地建库和blast比对。 blastn 用核苷酸序列检索核苷酸数据库 blastp 用蛋白质序列检索蛋白质数据库 blastx 核苷酸序列通过6种阅读框翻译不同蛋白序列后,检索蛋白数据库 tblastn 蛋白序列比对核酸库,核酸数据库通过6种开放阅读框翻译不同蛋白质 tblastx 核酸序列和核酸数据库都通过6种开放阅读框翻译后比对 平常我们用的最多的就是blastn和blastp,进入网页,选择blast方式,然后贴上自己的quary序列,选择数据库,选择比对的物种,设置参数如E值,wordlength长度等等。但是NCBI网站的BLAST在线工具有个让人特别无语的缺点:国内访问速度巨慢!不仅仅是比对过程慢,一条序列还好,大批量数据比对就不要想了,有的时候网页都打不开一直转圈圈。因此本地化blast工具还是很有必要的。 好在NCBI很贴心地提供了blast+工具,我们安装好blast+工具和下载好数据库以后,就可以不依赖网页和NCBI地服务器,在本地服务器上运行了。 1 安装blast+最新版blast+工具可以通过ftp方式获得,点击这里 我是在集群账户下安装,集群机器都是linux操作系统的,因此我选择的最新linux版本 别忘了要连md5校验文件一起下载,谁都不知道下载过程中是否会出错,因此大的文件下载完以后都是需要验证文件完整性的! 将上面的ftp地址拼凑一下,网速好的话可以直接用wget下载,但是我这边服务器连NCBI网速实在太慢了,wget只有10Kb/s的速度,甚至还会断开重连。看的我高血压都要犯了,无奈之下挂了个梯子,在自己电脑上下载好这两份文件,通过xftp传到了服务器上。 在服务器上首先校验文件完整性: md5sum -c ncbi-blast-2.13.0+-x64-linux.tar.gz.md5 显示结果OK后,解压: tar -zxvf ncbi-blast-2.13.0+-x64-linux.tar.gz 名字太长了,不方便以后找,顺便改个名就叫blast: mv ncbi-blast-2.13.0+-x64-linux.tar.gz blast 然后是配置环境变量: 123vim ~/.bashrc # 编辑环境变量文件# 在.bashrc文件最后一行加入如下内容(根据自己路径修改)export PATH="/public/home/wlxie/blast/bin:$PATH" 保存退出后重新source一下.bashrc文件,blast+工具就安装好了。 2 下载nr/nt数据库我们比对一般用的是NCBI的非冗余蛋白/核酸数据库,有两种方法下载nr/nt数据库: 1.通过blast+工具自带的更新程序下载 2.通过aspera工具下载 同样是网速的问题,如果用第一种方法下载,我们可以在~/blast/bin目录下找到如下的perl程序 直接运行命令 perl update_blastdb.pl nt 但是10几kb/s的速度真的让人抓狂,所以我推荐第二种方法:用IBM公司开发的快速下载神器——aspera 安装aspera在我之前写的一篇博客里推荐过sra-tools工具中的prefetch,用来下载SRA数据中存放的高通量测序原始数据。prefetch软件就是默认通过aspera工具进行下载的。 如果之前没有安装过aspera,可以用conda直接安装,命令如下: conda install -c hcc aspera-cli 这里注意下aspera-cli是aspera的命令行版本,各种不同版本的本质上下载都是调用ascp程序,并且需要openssh公钥认证,不同版本的aspera公钥文件存放的位置不同。因为我们是通过conda安装的aspera,aspera-cli公钥文件的位置在你的conda环境目录下的etc文件夹中,比如我的aspera-cli公钥文件在/public/home/wlxie/miniconda3/envs/biosoft/etc 而且因为是conda安装的,我们不需要修改什么配置文件和依赖关系,还是挺省事的。 用aspera下载数据库nr/ntnr/nt数据库也可以通过ftp方式获得,点击这里查看ftp网址 为了方便找到下载到本地的数据库,先在家目录新建db/blast文件夹,进入这个文件夹后,在当前目录下运行如下命令: ascp -QT -i /public/home/wlxie/miniconda3/envs/biosoft/etc/asperaweb_id_dsa.openssh -k1 -l 500m anonftp@ftp.ncbi.nlm.nih.gov:/blast/db/FASTA/nt.gz ./ 这里是其中一个nt数据库,nr数据库只要改一个字母就行了,两个数据库都要下载。 稍微解释下各参数的含义: -Q 用于自适应流量控制,磁盘限制所需;-T 是取消加密,否则有时候数据下载不了。两个参数是搭配一起使用的 -i 输入私钥文件,注意下载的ascp版本不一样文件位置也不一样 -k1 这里是加上了断点传续功能 -l 限制最大下载速度 后面一串是账户@ftp地址:路径。注意@和冒号。NCBI公共账号是anonftp,也就是你下载SRA数据库数据也可以用这个账号;EBI公共账号是era-fasp 最后指定下载文件的路径,我用了当前路径 可以看到下载速度杠杠的,提升了不知道多少倍…下载大数据都可以用ascp命令。 下载好之后同样别忘了校验md5文件,校验后gunzip直接解压到当前文件夹。 3 本地建库解压完成以后我们可以看到这两个数据库总大小在980G 现在还不能用这两个数据库,需要对这两个超大的数据文件建索引,也就是本地建库。 使用如下命令: makeblastdb -in nt -dbtype nucl -input_type fasta -out nt makeblastdb -in nr -dbtype prot -input_type fasta -out nr -in: 待格式化的序列文件 -dbtype: 数据类型,prot为蛋白序列,nucl为核酸序列 -input_type: 输入数据的类型,默认为fasta格式 -out: 自定义的数据库名称 这一步需要非常长时间,在目录下能看到有文件生成并且没有报错就行了,同样的操作方法可以用自己的基因组数据建库。 这里有两条核苷酸序列可能有问题,序列录到了开头第一行,不过就只有两条序列应该不影响。nt库录入了0.8亿条序列,nr库录入了4.8亿条序列。 4 创建blast全局配置文件在家目录下创建blast全局配置文件: 123456789101112131415161718192021222324252627282930313233$vim .ncbirc # 家目录下创建一个新文件.ncbirc,输入如下内容; Start the section for BLAST configuration[BLAST]; Specifies the path where BLAST databases are installedBLASTDB=/public/home/wlxie/db/blast; Specifies the data sources to use for automatic resolution; for sequence identifiersDATA_LOADERS=blastdb; Specifies the BLAST database to use resolve protein sequencesBLASTDB_PROT_DATA_LOADER=/public/home/wlxie/db/blast/nr; Specifies the BLAST database to use resolve protein sequencesBLASTDB_NUCL_DATA_LOADER=/public/home/wlxie/db/blast/ntBATCH_SIZE=10G; Windowmasker settings[WINDOW_MASKER]WINDOW_MASKER_PATH=/public/home/wlxie/db/blast/windowmasker; end of file 以上设置中定义了blastn和blastp默认的地址,这样我们在比对数据库的时候可以直接输入数据库的名称而不用给出绝对路径,方便一点(这步不是必须的,可选)。 5 运行blast程序以上准备工作完成后,准备一段query序列试一下,我的query序列名称是gene.fna 运行blastn程序: blastn -query gene.fna -out gene_blastn_nr.out -db nt -outfmt 6 -evalue 1e-5 -num_threads 10 -query: 用来查询的输入序列 -db: 指定的数据库名称 -out: 自定义输出的结果文件,最好统一格式。我是基因名_比对方法_数据库.out,这样比较直观知道比对了什么,怎么比对的 -outfmt: blast结果的呈现形式,一般用6比较多,也就是m8格式,以制表符为分隔符,有部分信息会缺失。5是XML格式比较适合解析,7在6基础上加了表头。 -evalue: 限定E值 -num_threads: 指定多少个核运行blast程序 还有其他参数比如就不一一介绍了,说明一下,一个序列的blast可以用上面的命令,多个序列的blast同样适用,把多个fasta格式的序列放进去即可。 当然,批量blast的结果需要限定匹配的结果数量,毕竟我们不可能几百上千个序列一一查看,可以指定参数-max_target_seqs 5限制每个序列的最大匹配数量,这个数值推荐是在5以上,5以下会有警告信息。 blast结果m8格式如下: 一共12列,分别能获得如下信息: 1、Query id:查询序列ID标识 2、Subject id:比对上的目标序列ID标识 3、% identity:序列比对的一致性百分比 4、alignment length:符合比对的比对区域的长度 5、mismatches:比对区域的错配数 6、gap openings:比对区域的gap数目 7、q. start:比对区域在查询序列(Query id)上的起始位点 8、q. end:比对区域在查询序列(Query id)上的终止位点 9、s. start:比对区域在目标序列(Subject id)上的起始位点 10、s. end:比对区域在目标序列(Subject id)上的终止位点 11、e-value:比对结果的期望值,解释是大概多少次随即比对才能出现一次这个score,Evalue越小,表明这种情况从概率上越不可能发生,那么发生了即说明这更有可能是真实的相似序列 12、bit score:比对结果的bit score值,越高越好","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"}],"tags":[{"name":"BLAST+","slug":"BLAST","permalink":"http://www.shelven.com/tags/BLAST/"},{"name":"aspera","slug":"aspera","permalink":"http://www.shelven.com/tags/aspera/"}]},{"title":"perl语言学习笔记(1)","slug":"perl语言学习笔记(1)","date":"2022-06-15T16:07:31.000Z","updated":"2022-12-03T16:06:51.000Z","comments":true,"path":"2022/06/16/a.html","link":"","permalink":"http://www.shelven.com/2022/06/16/a.html","excerpt":"最近在处理三代测序的下机数据,用到了一些挺好用的perl脚本,但是苦于没接触这种类型的编程语言,想根据情况改一些代码却看不懂实现方式= =","text":"最近在处理三代测序的下机数据,用到了一些挺好用的perl脚本,但是苦于没接触这种类型的编程语言,想根据情况改一些代码却看不懂实现方式= = 前面说学习perl可以只学怎么调用模块,马上啪啪打脸了,这货和python还是有点不一样的,还是抽空补补基础吧~现在做生信用的最多的就是R、python和perl,多掌握一门编程语言还是挺有必要的。记录一下自学的过程和笔记,自学视频来源是b站up主生信技能树-jimmy 示例首先了解一下perl脚本的结构,以blast结果过滤的perl脚本为例,输入文件blast_m8.out是一个12列,分隔符为空格的文件: 123456789101112131415#!/bin/perl -w # 选择解释器类型为perl,-w是运行错误时提供警告信息open IN,"blast_m8.out"; # 打开文件,m8格式输出的blastout文件。每次只读一行while (<IN>) { # 大括号为程序块,每个程序块是一个独立的部分,执行相对独立的功能 chomp; # 去掉读进来数据结尾的换行符\\n(没有换行符则不起作用) my @line=split /\\s+/,$_; # 以空白分割(\\s是匹配任何空白符,+表示匹配任意多个),存入数组中 if ($line[2] >=50 && $line[3] >=100) { print "$_\\n"; # 将第三列identity值大于50,第四列序列长度大于100的blast结果输出 } else { next; # 否则进入下一个循环 }}close IN;# 这个脚本第四行的my也要注意下,一般是需要申明use strict;也就是使用严谨的方式,在这种方式下,任何变量都必须先定义,不使用my的话定义第一次出现的$和@运行会报错。初学可以不使用use strict。# $_这个变量是使用非常多的,如果没有定义变量名称,则默认使用$_ OK,格式与python不一样,以分号作为每一行结尾,基础语法类似但不完全一样,先从基础学起。 1 标量数据标量数据特点 perl中最基本的数据类型 可以是数字、字母 无需定义类型(所有perl语言的数据都是双精度浮点型,不需要对数据类型进行定义,代价是消耗内存) “单数为标量” 字符串运算 字符串就是一连串字符组合,可以是字母数字标点等 对DNA序列处理本质上就是处理字符串 字符串可以为空 需要“引号”,尽量使用双引号 字符串连接“.”或者“x” 如 “hello” . “world” 标量变量标量变量用来动态存储标量值,以美元符号$表示(定义数组用@符号),和linux一样不能以数字开头 123456# 一个=表示赋值,两个==表示判断,这里和python是一样的$gene_num=3$gene_num=$gene_num+4$gene_num+=4 # 双目运算符,运算所需变量为两个运算符,这个简写是非常常用的$dna="ATCGGGTATCG"$dna.="ATCGGGTCG" # 双目运算符同样可以用于字符串操作,得到标量变量为连接的字符串 2 数组和列表数组构建列表(list)指标量的有序集合,数组(array)则是存储列表的变量 1234567@array=(1,"hello",undef,$dna,5); # 左边为数组,右边为列表,构建列表中间用逗号隔开$array[0]=1;$array[1]="hello" # 注意下标数字从0开始,0表示第一个元素数组的最后一个元素角标$#array,因此数组元素个数=$#array+1@number=(1..100) # 范围操作符(..)每次加一@string=qw (fred barney betty wilma dino) # qw操作符可以省略逗号 split和join函数 split将字符串根据固定的分隔符进行切割,切割后得到一个数组 join与split相反,将数组连接成一个标量 12345678#!/bin/perl$scalar="a:bcd:123:de";@array=split /:/,$scalar; # 以冒号作为分隔符分割print "@array\\n"$new_scalar=join "\\t",@array; # 以制表符作为分割符,好处是excel里打开每个元素在不同单元格print "$new_scalar\\n" pop和push函数12345#!/bin/perl@number=(1,2,3,4,5);$value=pop @number; # pop提取数组的最后一个元素push @number,6; # push添加一个元素到数组的末尾print "$value\\n@number\\n"; shift和unshift函数12345#!/bin/perl@number=(1,2,3,4,5);$value=shift @number; # shift提取数组第一个元素unshift @number,10; # unshift添加一个元素到数组的开头print "$value\\n@number\\n"; 一般来说pop和shift用的比较多,一个提取数组末尾元素,一个提取数组开头元素。 因为我们用perl处理的往往是矩阵文件,第一行是ID信息,我们往往是读入一行数据,去掉换行符,存储为标量,分割数组。这个时候就要用shift函数,将ID提取出来,这样呢后面都是同一种类型的数据,方便我们操作,也就是底下这个框架: 1234567#!/bin/perlwhile (<IN>) { chomp; my @line=split /\\s+/,$_; my @id=shift @line; print "$id\\n"} sort和reverse函数1234#!/bin/perl@number=(1..10);@number_sort=sort @number; # sort函数使数组按照ASCII码大小排序print "@number_sort\\n"; 可以看到10在1的后面,因为sort函数是以ASCII码大小进行排序的。 1234#!/bin/perl$dna="ATCGCGTAGCATCGATGCTGATCATGC";$dna_reverse=reverse $dna; # reverse函数可以使数组或者字符串反转print "$dna_reverse\\n"; reverse函数在做DNA反向互补配对的时候能用到。 foreach遍历数组123456789101112#!/bin/perl@number=(1..10);foreach $num (@number) { # 遍历数组中的值依迭代 print "$num\\n";}# 也可以省略一部分内容#!/bin/perl@number=(1..10);foreach (@number) { # 省略了$num,存储到默认的$_中 print;}","categories":[{"name":"编程自学","slug":"编程自学","permalink":"http://www.shelven.com/categories/%E7%BC%96%E7%A8%8B%E8%87%AA%E5%AD%A6/"}],"tags":[{"name":"perl","slug":"perl","permalink":"http://www.shelven.com/tags/perl/"}]},{"title":"基因组注释文件gbff格式转换gff3格式","slug":"基因组注释文件gbff格式转换gff3格式","date":"2022-06-13T17:09:45.000Z","updated":"2022-12-03T16:19:26.000Z","comments":true,"path":"2022/06/14/a.html","link":"","permalink":"http://www.shelven.com/2022/06/14/a.html","excerpt":"前几天老师布置了一个任务,寻找夹竹桃科Apocynaceae分类下的物种参考基因组,我在plaBiPD网站和NCBI的genome数据库中只找到包括罗布麻在内的5个已发表物种参考基因组,且都是gbff格式的。提交之后被告知需要gff格式的,因为gbf格式中没有基因相关结构的位置信息。找了一个perl脚本完成了任务。","text":"前几天老师布置了一个任务,寻找夹竹桃科Apocynaceae分类下的物种参考基因组,我在plaBiPD网站和NCBI的genome数据库中只找到包括罗布麻在内的5个已发表物种参考基因组,且都是gbff格式的。提交之后被告知需要gff格式的,因为gbf格式中没有基因相关结构的位置信息。找了一个perl脚本完成了任务。 emmmmm….其实不是很理解,因为gbff格式和gff格式是可以相互转换的,如果gbff注释文件中有信息缺失,那么gff格式也同样没有相关信息….不管怎么样先转换个格式交差,网上搜了下有前人写的perl脚本可以用,但是我以前没接触过perl语言,这里做个笔记写一下自己瞎捣鼓的过程。 1. perl语言和CPAN模块库从百度百科中引用对perl语言的一段描述: Perl是一种功能丰富的计算机程序语言,易于使用、高效、完整,而不是美观(小巧,优雅,简约)。同时支持过程和面向对象编程,对文本处理具有强大的内置支持,并且拥有第三方模块集合之一。 Perl借取了C、sed、awk、shell脚本语言以及很多其他程序语言的特性,其中最重要的特性是它内部集成了正则表达式的功能,以及巨大的第三方代码库CPAN。 看到这个简介,个人的理解就是perl语言就像python一样,自己有丰富的第三方库可以调用。同样,我们不需要具体了解每个模块的实现方式和底层代码,只要知道会调用相关的模块实现自己的目标即可。 CPAN是perl的一个第三方源码模块库,里面有上百万的perl模块,用于支撑perl的各种功能。为了方便安装perl的各种模块,前人做了一个CPAN模块,用cpan命令来安装perl的各个模块。也可以通过cpan命令来安装bioperl模块,里面有非常多的有关生信分析的perl脚本。 2. 安装和配置bioperl运行gbff转换gff3的perl脚本需要调用bioperl的一些模块,因此第一步需要安装bioperl,以及配置相应的环境。 登录学校集群,发现系统自带安装了perl程序和CPAN模块,我们可以用CPAN来安装bioperl。 12# 在命令行进入CPAN交互式界面perl -MCPAN -e shell 进入CPAN交互式界面如下: 1234# 在线寻找bioperl安装包cpan>d /bioperl/# 安装对应版本的bioperl,注意目录和版本号根据搜出来的结果不同而不同cpan>install S/SE/SENDU/bioperl-1.5.2_100.tar.gz 之后就是漫长的安装过程了,所有依赖关系也会一并安装,安装的时间较长,大概需要半个小时。 安装完成之后是配置环境变量,不配置的话即使安装了bioperl,也会找不到对应的模块。我的bioperl安装路径是/public/home/wlxie/miniconda3/envs/biosoft/lib/perl5/site_perl/5.22.0/Bio/,可以通过echo $PERL5LIB来查看当前perl模块的调用路径,然后在家目录的.bashrc环境变量文件中将bioperl的模块路径加到perl模块调用的路径当中。 12# 在。bashrc文件最后一行加入bioperl模块调用路径export PERL5LIB="/public/home/wlxie/miniconda3/envs/biosoft/lib/perl5/site_perl/5.22.0/:$PERL5LIB" 这样所有的配置就完成了。 3. gbff格式转换gff3github上有许多gbff格式转gff3格式的脚本代码,有用biopython做的,也有bioperl做的,可能是我配置的问题,试了几个脚本后只有一个可以顺利转换。本来想研究一下脚本的实现方式,可是源代码2000多行看的我实在不知从何下手,这里只记录一下使用方法和备份脚本文件。 源代码的出处已经找不到了…点击这里查看源代码在本站的备份 使用方法: 将脚本文件bp_genbank2gff3.pl放在要转换格式的gbff文件同一个目录下,运行命令 12# perl bp_genbank2gff3.pl gbff文件名perl bp_genbank2gff3.pl Asclepias_syriaca-GCA_002018285.1_ASM201828v1_genomic.gbff 脚本自动运行,结束后会在当前目录生成同名的gff3文件","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"bioperl","slug":"bioperl","permalink":"http://www.shelven.com/tags/bioperl/"},{"name":"perl","slug":"perl","permalink":"http://www.shelven.com/tags/perl/"}]},{"title":"集群和slurm调度系统使用心得","slug":"集群和slurm调度系统使用心得","date":"2022-05-24T18:05:26.000Z","updated":"2023-02-24T03:53:40.000Z","comments":true,"path":"2022/05/25/a.html","link":"","permalink":"http://www.shelven.com/2022/05/25/a.html","excerpt":"随着各种组学技术和生物信息学技术的发展,高通量测序现在已经广泛应用到生命科学的多个领域,这些测序数据动辄几个G甚至几十上百G(主要看物种和测序深度),个人电脑对这些大量的数据处理时有些力不从心。","text":"随着各种组学技术和生物信息学技术的发展,高通量测序现在已经广泛应用到生命科学的多个领域,这些测序数据动辄几个G甚至几十上百G(主要看物种和测序深度),个人电脑对这些大量的数据处理时有些力不从心。 比如我的小破电脑,4核,2G运行内存,在对16个10X测序深度的拟南芥转录组数据进行回帖参考基因组时,cpu满载的情况下跑了整整一个晚上,拟南芥的参考基因组还是比较小的(只有116M大小)。因此在需要做大量数据处理的时候我们往往都会用到计算机集群。 先放有用的东西:slurm官网快速开始手册 以及武汉大学测绘学院做的中文手册:点击浏览和下载 1. 计算机集群介绍先上一段百度百科的介绍 计算机集群简称集群,是一种计算机系统, 它通过一组松散集成的计算机软件或硬件连接起来高度紧密地协作完成计算工作。在某种意义上,他们可以被看作是一台计算机。 集群系统中的单个计算机通常称为节点,通常通过局域网连接,但也有其它的可能连接方式。集群计算机通常用来改进单个计算机的计算速度和/或可靠性。一般情况下集群计算机比单个计算机,比如工作站或超级计算机性能价格比要高得多。 简单来说集群就是一群电脑连接在一起完成同一个工作,自然是比单个电脑工作效率高得多。 塔大有着南疆最大的超算中心,我用测试账号登录大致看了下节点配置信息和存储规模: 可以看到塔大集群有三个分区:debug、normal和operation,其中debug是默认分区。 每个分区下都有admin91、admin92和computer1-6共计8个节点,从状态栏可以看到admin91节点处于down状态,一般来说只有故障节点才会显示状态down,但是我看了下登录节点的hostname就是admin91(admin91是登录节点),可能就是这样设置的,防止用户申请到登录节点资源(学校规定禁止在登录节点运行脚本程序,为了防止用户占用太多登录节点资源)。computer1-6都是计算节点,可以看到状态栏是idle也就是空闲的。admin92是个胖节点,状态却是drain也就是不能分配,这个我不理解,如果要运行并行命令就要用到胖节点。 裸储存容量算个大概450TB左右,不算大不算小,够用就行。 顺便看一下各个节点的性能: 稍微计算一下可以发现,计算节点computer1-6每个节点有128个核,每个核2G运行内存;登录节点admin91有64个核,每个核4G运行内存(不是很理解登录节点为什么要这么豪华……);胖节点admin92有256个核,每个核4G运行内存。总的来说,放在内地比可能确实不怎么样,但是在新疆可以说是奢华顶配了…… 2. slurm调度系统介绍塔大集群用的是slurm调度系统,简单来说就是借助slurm这个资源管理系统,将超算中心的集群计算机统一管理。slurm是个开源分布式资源管理软件,管理这种大型的计算机集群还是比较高效的,比如天河二号上就使用了 该资源管理系统。集群操作和个人电脑操作不一样的地方是,我们需要申请计算节点然后才能运行计算的命令,需要了解一下slurm的作业调度系统。 了解一下基本概念:一个分区(partition)就是节点的逻辑分组,可以有不同的节点(node);可以调用几个节点的资源创建作业步(job step),一个作业(job)就是一次资源分配,可以有多个作业步并且可以并发运行。 再来看一下slurm调度系统的组成成分: 主要有控制进程slurmcld,记账存储进程Slurmdbd(有的集群是收费的),节点监控进程slurmd,作业管理进程slurmstepd和用户命令工具组成,我们可以不用了解这些进程之间的相互关系,熟悉用户命令比如创建作业,提交作业和查看作业状态即可。 2.1 创建和提交作业slurm作业调度系统有三种模式创建和提交作业:交互模式——srun,批处理模式——sbatch和分配模式——salloc,分别介绍一下~还是再次强调一下,学校集群禁止在登录界面直接运行计算命令,第一次发现会强制终止进程,第二次管理员会注销用户账号。 2.1.1 交互模式——srun交互模式说白了就是我们通过命令行与集群产生可以互动的“交流”,具体过程如下: 通过终端提交资源分配请求,指定资源数目和限制 等待资源分配 获得资源后,自动加载计算任务 运行中,任务I/O传递终端,可与任务进行交互 任务结束后,资源被释放 一次执行srun生成一个作业步,也就是一次任务加载,执行一次最简单的hostname命令如下: -n 参数指定核数,-w参数指定节点,因此显示的hostname就是computer3。运行结束后直接释放资源。 上面的例子可能没有体现出交互的意义,因为程序比较简单。有些程序在运行的过程中需要人为调整,srun才能体现出优势。 2.1.2 批处理模式——sbatch批处理模式顾名思义特点在于需要自己写批处理脚本,具体过程如下: 编写作业脚本,指定资源数目和限制 sbatch提交作业 作业排队等待资源分配 分配到资源后在首节点加载执行作业脚本 任务结束释放资源 运行结果定向到指定文件夹 这个模式也是用的最多的,登录塔大集群可以在用户家目录下找到job_example文件夹,里面有不同的slurm脚本提供参考,举个最简单的例子sleep.slurm,我们cat一下看看脚本内容: 第一行#! 指定解释器类型,和其他脚本都一样; 第二行开始后面几行的#SBATCH都被识别为sbatch命令,因此后面都加上了对应参数,这里只有sbatch这一行会被识别成命令,注意不是被注释了。如果脚本里写了对应参数内容,命令运行脚本的时候就可以不用加入这些参数。 第六行开始也就是#以后的部分,才是我们编写脚本要运行的命令。 参考这个格式,我写了如下一个建立拟南芥参考基因组索引文件的hisat2.slurm脚本: 申请了job名为job1,一个节点,一个核,显示队列,显示程序运行开始时间,运行程序,显示结束时间。 提交直接用sbatch hisat2.slurm,很快就获得资源跑完了程序,当前目录下生成了索引文件和默认输出文件slurm-job号.out 因为输出结果在结果文件里,所以我们屏幕上是看不到运行过程的,要想监控运行过程只能在运行时输入squeue查看,后面会说。运行结束后同样会自动释放资源。 2.1.3 分配模式——salloc分配模式相比前面两个模式更灵活一些,简单来说就是申请资源,执行运算任务后手动释放资源,可以用来在正式提交sbatch前做程序测试,检查代码正确后再写成脚本提交(不过也可以直接提交,不用sbatch)。流程如下: 提交资源分配请求 排队等待资源分配 命令行执行指定的命令 命令执行结束,exit手动释放资源 这个比较简单,直接salloc后面接参数申请指定的资源就行,最后一定要exit手动释放资源。 申请后可以用hostname命令看看申请的是哪个节点,确认是计算节点后再运行计算命令。 2.1.4 作业提交参数知道了三个模式的具体用法,只需要添加对应的参数就行了。没有人会具体记住所有参数,上面只有常用的几个需要记一下。这里记录一下其他常用提交参数: 参数 含义 类型 示例 -J 作业名,squeue看到的作业名 字符串 -J wrf;表示作业名称为“wrf” -n 作业申请的总cpu核心数 数值 -n 4;表示作业申请4个cpu核心 -N 作业申请的节点数 数值 -N 1 表示作业申请1个计算节点 -p 指定作业提交的分区 字符串 -psilicon表示将作业提交到silicon分区 -t 指定作业的执行时间 数值 -t 30 表示作业的执行时间不超过30分钟 -o 指定作业标准输出文件的名称 字符串 -o %j,表示使用作业号作为作业标准输出文件的名称 -e 指定作业标准错误输出文件名称 字符串 -e %j,表示使用作业号作为作业标准错误输出文件名 -w 指定分配特定的计算节点 字符串 -w computer3 表示使用computer3节点 -d 作业依赖关系设置 字符串 -d after:123 表示本作业须待作业123开始以后再执行 顺便最后再提一下交互模式和批处理模式是可以相互结合的,脚本中可以加入srun命令,可以自行尝试。 如上,可以用sbatch一次性创建100个job,每个job运行一次srun后面的内容,也就是提交并行的100个计算作业。也可以不加–array这个参数,srun最后加上符号&在后台挂起,下一行继续用srun命令,以此来实现并行计算,不过要注意最后一行命令用wait等待所有命令运行结束后一起结束,否则读完sbatch就结束了。 2.2 其他用户命令这里再简单列举记录一些常用的用户命令和参数,方便自己后续用到时查阅~ 2.2.1 sinfo 查询信息 参数 含义 -a 查看所有分区信息(含隐藏分区) -d 查看dead状态(通信异常)的节点和分区的信息,与-r参数对应 -l 打印分区(或节点)的详细信息 -n 查看指定节点的信息 -p 查看指定分区的状态 -r 查看计算节点(内部通信)正常的节点和分区的状态,与-d参数对应 -R 查看节点不可用的原因,包括管理操作设置的异常 -t 查询指定节点状态的分区或节点的信息 –federation 显示所有集群的分区(或节点)的信息 –local 仅显示当前集群的分区(或节点)的信息 2.2.2 squeue 查询作业 参数 含义 -A 查看指定账号的作业 -a 显示所有分区(包含隐藏)下的作业和作业步 -r 按行显示作业组的每一个作业 –hide 不显示隐藏分区或者无权访问的分区中的作业 -j 根据指定的作业号查询作业信息 -l 长格式显示作业信息 –federation 显示所有集群下的作业 –local 仅仅查看当前集群的作业 -n 按作业名查询作业或作业步 -p 按分区查询作业 -s 查询作业步 -t 指定要显示的作业的状态 2.2.3 scancel 删除作业 参数 含义 <job id> 删除指定job id作业 –t 删除指定状态的作业 –account= 删除指定账号的作业 –name= 删除指定名称的作业 –partition= 删除指定分区的作业 –reservation= 删除指定预约名称的作业 –user= 删除指定用户的作业 –nodelist= 删除指定节点的作业 2.2.4 scontrol 查询详细信息这个命令和sinfo相比更为详细,主要能获得4个方面的详细信息 命令 含义 scontrol show node <name> 查询指定节点信息 scontrol show partition <name> 查询指定分区信息 scontrol show job 查询job信息,注意是所有的 scontrol show config 查询配置信息,也是所有的 2023/02/23 补充因为经常要用到scontrol show job来查看当前作业得运行情况、占用资源情况等等,因此更新一下该命令主要输出项: 参数名 解释 JobId 作业号 UserId 用户名(用户ID) GroupId 用户组(组ID) Priority 优先级,越大越优先,如果为0则表示被管理员挂起,不允许运行 Nice Nice值,越小越优先,­20到19 Account 记账用户名 JobState 作业状态。– PENDING:排队中;– RUNNING:运行中;– COMPLETED:已完成;– CANCELLED:已取消 Requeue 节点失效时,是否重排队,0为否,1为是 Restarts 失败时,是否重运行,0为否,1为是 BatchFlag 是否为批处理作业,0为否,1为是 Reboot 节点空闲时是否重启节点,0为否,1为是 RunTime 已运行时间 TimeLimit 作业允许的剩余运行时间 TimeMin 最小时间 SubmitTime 提交时间 EligibleTime 获得认可时间 StartTime 开始运行时间 EndTime 预计结束时间 Partition 队列名 AllocNode:Sid 分配的节点:系统ID号 NodeList 实际运行节点列表 BatchHost 批处理节点名 NumNodes 节点数 NumCPUs CPU核数 NumTasks 任务数 Command 作业命令 WorkDir 工作目录 StdErr 标准出错输出文件 StdIn 标准输入文件 StdOut 标准输出文件","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"集群","slug":"集群","permalink":"http://www.shelven.com/tags/%E9%9B%86%E7%BE%A4/"},{"name":"slurm","slug":"slurm","permalink":"http://www.shelven.com/tags/slurm/"}]},{"title":"转录组数据分析笔记(10)——初识GO/KEGG富集分析","slug":"转录组数据分析笔记(10)——初识GO-KEGG富集分析","date":"2022-05-15T19:47:39.000Z","updated":"2022-12-03T16:41:25.000Z","comments":true,"path":"2022/05/16/a.html","link":"","permalink":"http://www.shelven.com/2022/05/16/a.html","excerpt":"前面介绍了如何找到差异基因,我们通过R包DESeq2获得了差异表达基因,在此基础上做了更为直观的火山图和差异表达基因热图。但是仅仅知道差异表达基因的名字还不够,我们还要知道它到底有哪些功能和特征,就比如我看到一个很养眼的动漫角色,我就要去查查出自哪部番,是怎么样的人设和背景故事,一样的道理。这里简单记录下如何使用AnnotationHub,以及怎么进行GO\\KEGG富集分析。","text":"前面介绍了如何找到差异基因,我们通过R包DESeq2获得了差异表达基因,在此基础上做了更为直观的火山图和差异表达基因热图。但是仅仅知道差异表达基因的名字还不够,我们还要知道它到底有哪些功能和特征,就比如我看到一个很养眼的动漫角色,我就要去查查出自哪部番,是怎么样的人设和背景故事,一样的道理。这里简单记录下如何使用AnnotationHub,以及怎么进行GO\\KEGG富集分析。 一个基因没有注释信息,那就只是一段核苷酸序列,有了注释信息我们才能知道这个基因在染色体上的定位,在具体的某个代谢途径上发挥什么功能等等。网上能找到很多注释信息的数据库,比如模式生物拟南芥TAIR,人类基因组hg19等等,Bioconductor有一个专门用来搜集注释信息数据库的工具包——AnnotationHub。 1. AnnotationHub注释数据库搜索工具用bioconductor下载Annotationhub包,载入(注:为演示结果,以下命令均在Rstudio终端输入) 123456789101112131415161718> library("Annotationhub")> hub <- AnnotationHub()C:\\Users\\HUAWEI\\AppData\\Local/R/cache/R/AnnotationHub does not exist, create directory? (yes/no): yes |===================================================| 100%snapshotDate(): 2021-10-20> hubAnnotationHub with 62386 records# snapshotDate(): 2021-10-20# $dataprovider: Ensembl, BroadInstitute, UCSC, ftp://ftp...# $species: Homo sapiens, Mus musculus, Drosophila melano...# $rdataclass: GRanges, TwoBitFile, BigWigFile, EnsDb, Rl...# additional mcols(): taxonomyid, genome,# description, coordinate_1_based, maintainer,# rdatadateadded, preparerclass, tags, rdatapath,# sourceurl, sourcetype # retrieve records with, e.g., 'object[["AH5012"]]' 第一次使用AnnotationHub需要创建一个AnnotationHub对象。为了更直观地使用,我们将AnnotationHub对象赋值给hub变量。查看这个变量,我们可以得到如下的信息。 数据库版本是2021-10-20,目前有62386条记录 可以用$dataprovider 方式查看数据来源,比如数据来自于Ensembl,UCSC等等 可以用$species 方式查看数据库有哪些物种,比如人类、小鼠等等 可以用$rdataclass 方式查看数据类型 可以通过函数mcols()查看更多信息 获取数据的方式是object[["AH5012"]] object指你命名的变量名 以上就是AnnotationHub的标准用法,比如我想获得拟南芥的注释数据库,我就输入以下命令查找: 123456789101112131415161718192021222324> query(hub, "Arabidopsis thaliana")AnnotationHub with 13 records# snapshotDate(): 2021-10-20# $dataprovider: UCSC, PathBank, NCBI,DBCLS, FANTOM5,DLRP...# $species: Arabidopsis thaliana# $rdataclass: SQLiteFile, TxDb, Tibble, list, OrgDb, Inp...# additional mcols(): taxonomyid, genome,# description, coordinate_1_based, maintainer,# rdatadateadded, preparerclass, tags, rdatapath,# sourceurl, sourcetype # retrieve records with, e.g., 'object[["AH10456"]]' title AH10456 | hom.Arabidopsis_thaliana.inp8.sqlite AH52245 | TxDb.Athaliana.BioMart.plantsmart22.sqlite AH52246 | TxDb.Athaliana.BioMart.plantsmart25.sqlite AH52247 | TxDb.Athaliana.BioMart.plantsmart28.sqlite AH87070 | pathbank_Arabidopsis_thaliana_metabolites.rda ... ... AH91794 | wikipathways_Arabidopsis_thaliana_metabolites... AH95585 | Alternative Splicing Annotation for Arabidops... AH95951 | org.At.tair.db.sqlite AH97723 | LRBaseDb for Arabidopsis thaliana (Thale cres... AH97844 | MeSHDb for Arabidopsis thaliana (Thale cress,... query()函数查找,输入拟南芥的学名Arabidopsis thaliana我们可以看到一共找出了13个数据库。可以看到AH95951这个编号的数据库来源就是最大的拟南芥数据库TAIR(OrgDb,存储不同数据库基因ID之间对应关系,以及基因与GO等注释的对应关系,后面ID转换和GO分析要用到),我们就用这个数据库的注释资源。 1234> hub[["AH95951"]]downloading 1 resourcesretrieving 1 resource | | 0% 前面说过object[["AH5012"]]是获取数据的方式。以上,就可以挂后台自动下载了。我这里因为网速的原因不下了,从前面的基因名也能看出来,我筛选的差异基因都是AT开头的,而且之前的基因组注释文件也是TAIR下载的,我可以直接用bioconductor安装org.At.tair.db包,这里用AnnotationHub只是提供一个找注释数据库的思路。 2. GO/KEGG富集分析2.1 基因ID转换找到和下载注释数据库只是第一步,接下来GO/KEGG富集分析需要用到R包clusterProfiler和org.At.tair.db 先来看一下我们基因名是什么格式的: 很明显,我们的基因ID是TAIR类型(废话,我从TAIR下的),org.At.tair.db包可以转换基因ID类型 可以用keytypes(org.At.tair.db)或者columns(org.At.tair.db)查看可以转换的基因ID类型 转换基因ID代码如下: 12345678library("org.At.tair.db")columns(org.At.tair.db) # 查看能转换基因的ID类型diffgen <- nDEGs[, 1] # 注意只需要基因名diff_gen <- bitr(diffgen, fromType = "TAIR", toType = "ENTREZID", # 基因ID类型TAIR转换为ENTREZID OrgDb = "org.At.tair.db") # 该函数是基于org.At.tair.db包的diff_gen 这一步我的基因ID转换率只有60%左右,有将近一半的TAIR基因ID不能成功转换成ENTREZID,可能是Gene ID的版本问题,同一个基因在不同版本genecode中结果不一样,下载的注释文件原始版本我这里找不到了…暂时无法解决这个问题。只能不转换基因ID先跑一遍GO/KEGG富集分析。 看了很多教程都说clusterProfiler需要的ID类型是ENTREZID,这里我持怀疑态度,不转换后续也能得到结果,我看了函数enrichGO()默认的基因ID是ENTREZID并不代表不能改变,有可能是误传。查阅了一些资料,简单来说Entrez ID是来自于NCBI旗下Entrez gene数据库的编号系统,基因编号系统之间是可以相互转换的,这些ID可以在对应的数据库找到基因注释信息,就是说也可以在网页上手动注释。 2.2 GO/KEGG分析123456789101112131415161718192021library("clusterProfiler")# GO富集分析enrich_GO <- enrichGO(gene = diffgen, # 基因名列表 OrgDb = 'org.At.tair.db', # 输入OrgDb数据库(注释对象信息) keyType = 'TAIR', # 输入的基因名ID类型 ont = 'ALL', # 输出的GO分类 pAdjustMethod = 'fdr', pvalueCutoff = 0.05, qvalueCutoff = 0.2, readable = TRUE)GO_result <- enrich_GO@resultwrite.table(GO_result, 'GO_result.csv', sep = ',', quote = FALSE, row.names = FALSE)# KEGG富集分析enrich_KEGG <- enrichKEGG(gene = diffgen, keyType = "kegg", organism = "ath", # 输入的物种名 pvalueCutoff = 0.05, qvalueCutoff = 0.2)KEGG_result <- enrich_KEGG@resultwrite.table(KEGG_result, 'KEGG_result.csv', sep = ',', quote = FALSE, row.names = FALSE) clusterProfiler这个包进行GO和KEGG富集分析就这两个函数 这里我的GO只富集到两条细胞组分的内容: 说一下各列代表的意思: ONTOLOGY GO分类BP(生物学过程)、CC(细胞组分)或MF(分子功能) ID 富集到的GO id号 Description 富集到的GO描述 GeneRatio和BgRatio 分别为富集到该GO条目中的基因数目/给定基因的总数目,以及该条目中背景基因总数目/该物种所有已知的GO功能基因数目 pvalue、p.adjust和qvalue p值、校正后p值和q值信息 geneID和Count,富集到该GO条目中的基因名称和数目 KEGG富集分析结果表如下: ID和Description 分别代表富集到KEGG的ID和描述,其他和GO富集都类似 KEGG富集分析的时候有一点需要注意,输入的organism名称需要在官网的KEGG Organisms列表中能找到,否则是不能进行分析的!点击这里进入KEGG Organisms: Complete Genomes 还发现一个很奇怪的问题,我在官网的Organisms列表能找到拟南芥Arabidopsis thaliana,但是在上面的函数中对参数赋值organism = "Arabidopsis thaliana"会显示HTTP 400错误,也就是发出的url请求有问题,但是输入organism = "ath"程序可以正常运行,以后注意写缩写吧(应该是只有缩写才行,会通过联网自动获取该物种的pathway注释信息)。 3. 可视化clusterProfiler包还提供了GO/KEGG富集结果的可视化方案,此处代码参考CSDN,作者:Tian問 这里因为我的GO结果不好,只简单写一下流程,详细作图函数参数使用方法和效果同样可以参考上面的链接~ 12345678910111213141516171819202122232425## GO富集分析可视化#barplotbarplot(enrich_GO, showCategory = 10)#dotplotdotplot(enrich_GO, showCategory = 10)#DAG有向无环图plotGOgraph(enrich_GO) #矩形代表富集到的top10个GO terms, 颜色从黄色过滤到红色,对应p值从大到小。#igraph布局的DAGgoplot(enrich_GO)#GO terms关系网络图(通过差异基因关联)emapplot(enrich_GO, showCategory = 30)#GO term与差异基因关系网络图cnetplot(enrich_GO, showCategory = 5)## KEGG富集分析可视化#barplotbarplot(enrich_KEGG, showCategory = 10)#dotplotdotplot(enrich_KEGG, showCategory = 10)#pathway关系网络图(通过差异基因关联)emapplot(enrich_KEGG, showCategory = 30)#pathway与差异基因关系网络图cnetplot(enrich_KEGG, showCategory = 5)#pathway映射browseKEGG(enrich_KEGG, "ath03060") #在pathway通路图上标记富集到的基因,会弹出页面链接到KEGG官网 关于GO/KEGG富集分析,还有非常多的操作和应用,我只是简单做个最基础的富集分析的学习,没有涉及到手动注释,构建orgdb等等更多操作。电脑快没电了,这篇笔记先暂时记这些,以后需要用到再补充~","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"转录组数据分析","slug":"转录组数据分析","permalink":"http://www.shelven.com/categories/%E8%BD%AC%E5%BD%95%E7%BB%84%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"}],"tags":[{"name":"AnnotationHub","slug":"AnnotationHub","permalink":"http://www.shelven.com/tags/AnnotationHub/"},{"name":"GO/KEGG","slug":"GO-KEGG","permalink":"http://www.shelven.com/tags/GO-KEGG/"},{"name":"org.At.tair.db","slug":"org-At-tair-db","permalink":"http://www.shelven.com/tags/org-At-tair-db/"}]},{"title":"视频一键转字符动画——python函数封装和调用练习","slug":"视频一键转字符动画——python函数封装和调用练习","date":"2022-05-07T17:43:17.000Z","updated":"2022-12-03T16:25:24.000Z","comments":true,"path":"2022/05/08/a.html","link":"","permalink":"http://www.shelven.com/2022/05/08/a.html","excerpt":"捣鼓了几天python代码,我现在也越来越发现python的魅力所在,它的强大之处在于有非常多的第三方库可以随意调用。我不需要知道这些第三方库各种函数的实现方式,只要知道这些函数有什么作用,能得到什么结果。只要构思好自己的想法,找到对应的库就可以一步步按照我的思路编写程序,实现我想要的结果,整个构思到实现的过程让我非常愉悦~","text":"捣鼓了几天python代码,我现在也越来越发现python的魅力所在,它的强大之处在于有非常多的第三方库可以随意调用。我不需要知道这些第三方库各种函数的实现方式,只要知道这些函数有什么作用,能得到什么结果。只要构思好自己的想法,找到对应的库就可以一步步按照我的思路编写程序,实现我想要的结果,整个构思到实现的过程让我非常愉悦~ 1. 前言写这篇博客纯粹是个人爱好,也是一个巧合~ 前几天刷b站看到有人做了个剪影的字符动画,我就很好奇python是否可以实现。参考了一下github上大佬们的图片转字符画的代码,对这些代码做了点深入研究,总算搞明白了其实现方式,并且自己动手修改代码,在原有基础上改了几个bug,新增几个模块的调用,最后一步封装写成了下面这个脚本。这个脚本的功能是只要输入视频文件和你想要的视频帧率,就可以自动将视频转化为字符动画。 可以先看一下视频效果~ 或者点击这里进入b站观看 Your browser does not support the video tag. 2. 实现思路 调用ffmpeg根据帧率将视频切割成图片 调用pillow库作图,每张图片转换字符画 调用ffmpeg合并字符画并输出动画 3. 脚本代码及详解思路很清晰,接下来是写代码实现的过程,细节方面需要调用其他库 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182from PIL import Image, ImageDraw, ImageFont # pillow库作图import subprocess # 执行命令行命令,为了调用ffmpegimport sysimport os # 操作目录用import shutil # 删除目录用import numpy as np # 转化numpy数组import gc # 优化运行内存用到# 定义输入值,这个脚本需要两个输入值:file_input和FPSfile_input = sys.argv[1]FPS = sys.argv[2]def do_turn(file_input, FPS): # 调用ffmpeg切割视频 os.makedirs("tempfile/cut/") # 当前目录新建存放切割图片的临时文件夹 shell_vedio = "ffmpeg -i " + file_input + " -r " + FPS + " -qscale:v 2 ./tempfile/cut/%05d.jpg" # 按照XXXXX序号切割 shell_voice = "ffmpeg -i " + file_input + " ./tempfile/out.mp3" subprocess.call(shell_vedio, shell=True) # 切割视频 subprocess.call(shell_voice, shell=True) # 分离音频 count =0 for file in os.listdir("./tempfile/cut/"): count += 1 print("成功分离音频,截图开始转换字符画......" + "共计" + str(count) + "张") # 统计切割图张数 # 图片转换字符画 list_p = os.listdir("./tempfile/cut/") cwd = os.getcwd() os.mkdir("./tempfile/new/") # 新建存放字符画的临时文件夹 process = 1 # 统计完成转换的字符画数量 for id in list_p: # 遍历cut文件夹所有切割后的图片做字符画转换 address = str("".join(cwd + '/tempfile/cut/' + id)) # 拼接文件的绝对路径 im = Image.open(address) # 调用image打开图片 font = ImageFont.truetype("DejaVuSans-Bold", size=20) # 字体模式,可更改 rate = 0.1 # 缩放比(不调整的话像素点过多,这里统一调整) aspect_ratio = font.getsize("x")[0] / font.getsize("x")[1] # 获得字符长宽比 new_im_size = np.array([im.size[0] * rate, im.size[1] * rate * aspect_ratio]).astype(int) # 转换numpy数组,调整大小 im = im.resize(new_im_size) im = np.array(im.convert("L")) # 转换灰阶图,生成numpy数组 symbols = np.array(list(" .-vM@")) # 建立字符索引,注意要按照亮度手动排序 if im.max() == im.min(): # 全黑和全是一种颜色进行区分 if im.max() > 0: # 全是一种颜色,亮度值大于0,则全部用最亮的字符数值 im = (im / im) * (symbols.size - 1) else: im[np.isnan(im)] = 0 # 全黑时亮度值为NaN(非数值),则全部用最暗字符的字符数值,也就是全黑 else: im = (im - im.min()) / (im.max() - im.min()) * (symbols.size - 1) # 根据索引赋予相应像素点相应的数值 ascii = symbols[im.astype(int)] letter_size = font.getsize("x") # 获取字符大小,与前面一定要对应 im_out_size = new_im_size * letter_size # 这里乘以字符长宽,否则字符只有一个像素点大小 im_out = Image.new("RGB", tuple(im_out_size), "black") # 设置输出图片,背景 draw = ImageDraw.Draw(im_out) y = 0 # 两个循环穷举赋值,做字符画图片 for i, m in enumerate(ascii): for j, n in enumerate(m): draw.text((letter_size[0] * j, y), n, font=font) y += letter_size[1] # 注意+=,这里赋值字符宽度给y值 im_out.save("./tempfile/new/" + id + ".png") # 定义输出位置和图片格式 print(address + "转换成功!当前进度:" + str(process) + "/" + str(count)) # 显示进度 process += 1 gc.collect() # 重要!每次循环结束释放一次内存,否则容易内存溢出 print("转换成功!开始生成视频,请稍候......") # 调用ffmpeg合并字符画为视频,并且合并分离的音频 outvedio = "ffmpeg -r " + FPS + " -i ./tempfile/new/%05d.jpg.png ./tempfile/out.mp4" subprocess.call(outvedio, shell=True) final_vedio = "ffmpeg -i ./tempfile/out.mp4 -i ./tempfile/out.mp3 final.mp4" subprocess.call(final_vedio, shell=True) shutil.rmtree("./tempfile") # 删除临时文件夹 print("字符动画final.mp4已生成!已移除临时文件夹")# 输入格式错误则显示该条用法def usage(): print("usage:", sys.argv[0], "<file_input> <FPS>") exit(0)if __name__ == "__main__": # 封装,只有在文件作为脚本直接执行时后面的语句才会被执行,而 import 到其他脚本中后面的语句是不会被执行的 if len(sys.argv) != 3: # 判断输入的值是否为两个,没错,是判断两个 usage() else: do_turn(file_input, FPS) 4. 注意要点调用numpy模块生成数组,是因为python本身虽然可以建立多维度的数组,但是书写起来非常麻烦。numpy可以很好地解决这个问题,可以理解为能构建一个更好用的数组。在对数组进行遍历穷举,要注意两次穷举分别生成两个数组,第二次生成的数组只有一个数,所以下面draw.text第二个参数text可以用n,也可以用n[0]列举第一个数。 12345y = 0 for i, m in enumerate(ascii): # 这里i是用不到的 for j, n in enumerate(m): draw.text((letter_size[0] * j, y), n, font=font) # 第一个参数是确定坐标 y += letter_size[1] 字符大小,字符格式,以什么字符为参照,都是可以调整的。只要注意一点,我们是按照像素点的亮度来赋于这个像素点用什么字符的,所以索引列比较重要,要自己按照字符亮度排序,添加字符注意改值。 1symbols = np.array(list(" .-vM@")) # 可以改成自己想要用的字符,注意按照亮度升序 还有,在计算亮度和赋予索引值的时候,我们是按照相对亮度来计算的。因此,当图片所有像素点都是一种颜色的时候,im.max() 和 im.min()值是相等的,相对亮度会出现0/0的值,导致报错。所以我加了以下判断条件:纯色黑色和其他颜色属于两种不同情况,黑色时numpy数组亮度是非数值NaN,需要将数组全部值进行替换为亮度最小的字符的值;因为是RGB取值,其他颜色值固定在0-255之间,颜色均一,相对亮度就没有意义了,因此全部调整为最亮字符的值。 1234567if im.max() == im.min(): if im.max() > 0: im = (im / im) * (symbols.size - 1) else: im[np.isnan(im)] = 0else: im = (im - im.min()) / (im.max() - im.min()) * (symbols.size - 1) 顺便再说一个很有意思的模块subprocess,subprocess.call()函数可以执行命令行的命令,并且这个命令是在子进程实行的,只有子程序结束才会继续执行其他命令,使用起来真的特别方便!比如有些程序我的python库里没有但是我的linux里有,在python脚本的某一步我需要用到linux里的软件去处理,这个时候就可以调用subprocess.call()函数去执行linux命令行的命令了。 还有一个函数虽然不显眼,但是起着至关重要的作用 gc.collect() 没有这个函数部分运行内存不够的电脑会崩……我在这里踩了个大坑…… 在对程序进行简化以后,我以为优化地差不多了,然后发现有的时候程序会被莫名其妙killed…… vi /var/log/messages 查看运行日志,好家伙,内存溢出了 经过一番度娘,我检查了一下自己也没有用到循环引用的变量啊,那么真相只有一个了:转换字符画部分程序有3个for循环嵌套,可能是for循环引用的对象没有及时回收导致内存不断增长,最后被系统kill掉(个人猜测)。虽然python本身有垃圾回收功能,而在程序运行的时候清理地并不是很及时,引入的gc模块是python的垃圾收集器模块,与默认的回收方式算法不同,gc.collect()函数可以强制进行垃圾回收。因此我在每个转化字符画的for循环执行一次结束后强制回收内存,如下: 效果是立竿见影的,内存占用再也没超过5%了,非常的稳定! 5. 食用方法缺什么第三方库就装什么,主要是pillow库、numpy库和ffmpeg,用conda可以直接安装。上面那段脚本代码复制粘贴,保存为ascii.py,运行命令: python ascii.py <视频文件> <你想要的视频帧率> 回车,OK,静静等屏幕上的提示就好了。视频文件不在当前文件夹的话自行加上绝对路径,完成以后只会在当前目录生成一个out.mp4的输出文件。 源代码将同步上传我的github。","categories":[{"name":"编程自学","slug":"编程自学","permalink":"http://www.shelven.com/categories/%E7%BC%96%E7%A8%8B%E8%87%AA%E5%AD%A6/"}],"tags":[{"name":"python","slug":"python","permalink":"http://www.shelven.com/tags/python/"},{"name":"ffmpeg","slug":"ffmpeg","permalink":"http://www.shelven.com/tags/ffmpeg/"},{"name":"numpy","slug":"numpy","permalink":"http://www.shelven.com/tags/numpy/"},{"name":"pillow","slug":"pillow","permalink":"http://www.shelven.com/tags/pillow/"}]},{"title":"转录组数据分析笔记(9)——pheatmap绘制差异表达基因热图","slug":"转录组数据分析笔记(9)——pheatmap绘制差异表达基因热图","date":"2022-05-04T17:16:14.000Z","updated":"2022-12-03T16:30:21.000Z","comments":true,"path":"2022/05/05/a.html","link":"","permalink":"http://www.shelven.com/2022/05/05/a.html","excerpt":"前面说到怎么用ggplot做一个火山图来查看各个基因的表达情况,火山图是以log2FC值为横坐标,以-log10(FDR)值作为纵坐标,将所有的基因都做了点状图。虽然能比较直观地看到所有基因表达情况,但我们真正感兴趣的是处理后差异表达的基因。因此,我们也可以通过前面得到的表达矩阵获得差异表达的基因名,对raw count数据进行提取和均一化,然后做一个差异基因的热图,能更直观地看到差异基因在各个样本中的上调下调情况。","text":"前面说到怎么用ggplot做一个火山图来查看各个基因的表达情况,火山图是以log2FC值为横坐标,以-log10(FDR)值作为纵坐标,将所有的基因都做了点状图。虽然能比较直观地看到所有基因表达情况,但我们真正感兴趣的是处理后差异表达的基因。因此,我们也可以通过前面得到的表达矩阵获得差异表达的基因名,对raw count数据进行提取和均一化,然后做一个差异基因的热图,能更直观地看到差异基因在各个样本中的上调下调情况。 做热图我们用的最多的R包是pheatmap,可以直接用biocmanager下载。 后面所有Rstudio操作都是在同一个Rproject中进行,引用的变量如果不理解就翻前面的笔记,最后我会把一整个转录组下游分析的R流程代码写成一个文件上传到github备份。 1. 热图介绍废话不多说,先上一段热图的定义介绍:热图是用来对采集的因子响应强度或其他的一些因素进行均一化,从而利用颜色条的变化来直观地表示不同样本之间的含量变化情况的图。 定义很简单,这里我们的因子响应强度就是每个基因的raw count值,但是raw count值从0到几千上万差别非常之大,作图不方便。所以我们通常会用均一化的方法,使每个基因的raw count值变化程度处于同一个数量级,再通过不同颜色变化得到基因在不同样品的含量变化。 R自带的均一化函数是scale(),注意下scale默认的均一化方式是按列进行的,我们还可以通过函数 t() 进行矩阵的行列转化,只需要将差异基因挑出来按行(也就是基因名)进行均一化,导入pheatmap包即可做成一个最简单的热图。 2. 简化版代码先上一个最最简易版的,比如我要分析前25个最可能发生差异表达的基因,代码如下: 1234567library("pheatmap")nDEGs <- gene[which(gene$group != "NOT_CHANGE"),] # 筛选差异基因sort_DEGs <- arrange(nDEGs,padj) # 按照padj值升序排序choose_gene <- head(sort_DEGs[,1],25) # 取padj值最小的前25个基因choose_matrix <- mycounts[choose_gene,] # 从raw count矩阵中挑出这25个基因数据heat_matrix <- t(scale(t(choose_matrix))) # 转换了两次行列并均一化,实际就是按row进行了均一化pheatmap(heat_matrix) # 以默认参数做热图 在plots窗口可以预览生成的热图: 因为没有加任何参数调整,所以不好看(已经比R自带的热图函数做出来的好看了),先解释一下上面代码实现的原理。gene是我们前面做火山图的矩阵,里面已经有了我们差异基因分组的一列group nDEGs <- gene[which(gene$group != "NOT_CHANGE"),] 这个代码是将group列中字符不等于“NOT_CHANGE”的数据挑出来赋值给nDEGs,注意下赋值后的nDEGs也是矩阵,可以直接查看。 sort_DEGs <- arrange(nDEGs,padj) arrange() 函数的功能是升序排列,这里按照padj值升序排列。 choose_gene <- head(sort_DEGs[,1],25) head()函数用法不说了,取了前25个基因。注意下我们取的第一列是基因名,如果你前面已经将基因名作为rownames导入了,那就要用rowname。 choose_matrix <- mycounts[choose_gene,],返回到我们前面的raw count矩阵,将基因名对应的数据挑出来,可以看下这个时候的choose_matrix矩阵是怎么样的: heat_matrix <- t(scale(t(choose_matrix))) 先进行一次行列转换,对列数据进行均一化,再进行一次行列转换,说白了就是对每行基因的raw count数据进行均一化,得到如下矩阵: pheatmap(heat_matrix)以默认参数做热图,大功告成。 如果要对所有差异表达的基因做热图,只需要修改一下输入的矩阵就行: 123all_matrix <- mycounts[(sort_DEGs[,1]),]heat_matrix_all <- t(scale(t(all_matrix)))pheatmap(heat_matrix_all) 因为这是默认参数作图,所以输出结果非常感人: 你论文敢用这种图?所以还是需要了解一下pheatmap包的各种参数,对热图进行调整和修改。 3. 参数详解此部分内容参考CSDN博客:跳动的喵尾巴 12345678910111213141516171819pheatmap(mat, color = colorRampPalette(rev(brewer.pal(n = 7, name = "RdYlBu")))(100), kmeans_k = NA, breaks = NA, border_color = "grey60", cellwidth = NA, cellheight = NA, scale = "none", cluster_rows = TRUE, cluster_cols = TRUE, clustering_distance_rows = "euclidean", clustering_distance_cols = "euclidean", clustering_method = "complete", clustering_callback = identity2, cutree_rows = NA, cutree_cols = NA, treeheight_row = ifelse((class(cluster_rows) == "hclust") || cluster_rows, 50, 0), treeheight_col = ifelse((class(cluster_cols) == "hclust") || cluster_cols, 50, 0), legend = TRUE, legend_breaks = NA, legend_labels = NA, annotation_row = NA, annotation_col = NA, annotation = NA, annotation_colors = NA, annotation_legend = TRUE, annotation_names_row = TRUE, annotation_names_col = TRUE, drop_levels = TRUE, show_rownames = T, show_colnames = T, main = NA, fontsize = 10, fontsize_row = fontsize, fontsize_col = fontsize, angle_col = c("270", "0", "45", "90", "315"), display_numbers = F, number_format = "%.2f", number_color = "grey30", fontsize_number = 0.8 * fontsize, gaps_row = NULL, gaps_col = NULL, labels_row = NULL, labels_col = NULL, filename = NA, width = NA, height = NA, silent = FALSE, na_col = "#DDDDDD", ...) 参数内容非常之多,我这里仅挑选一些可能用得上的做个记录: 参数 描述 color 表示热图颜色,colorRampPalette(rev(brewer.pal(n = 7, name = “RdYlBu”)))(100)表示颜色渐变调色板,“n” 的数量取决于调色板中颜色的数量,“name” 为调色板的名称,(100)表示100个等级;color = colorRampPalette(c(“blue”, “white”, “red”))(100)则是通过设置三种不同的颜色进行渐变显示 scale 表示进行均一化的方向,值为 “row”, “column” 或者”none” kmeans_k 默认为NA,即不会对行进行聚类;如果想在进行层次聚类之前,先对行特征(因子)进行 k-means 聚类,则可在此调整热图的行聚类数 cluster_rows 表示仅对行聚类,值为TRUE或FALSE cluster_cols 表示仅对列聚类,值为TRUE或FALSE clustering_distance_cols 表示列聚类使用的度量方法,与行聚类的度量方法一致 clustering_method 表示聚类方法,包括:‘ward’, ‘ward.D’, ‘ward.D2’, ‘single’, ‘complete’, ‘average’, ‘mcquitty’, ‘median’, ‘centroid’ cutree_rows 若进行了行聚类,根据行聚类数量分隔热图行 cutree_cols 若进行了列聚类,根据列聚类数量分隔热图列 treeheight_row 若进行了行聚类,其热图行的聚类树高度,默认为 “50” treeheight_col 若进行了列聚类,其热图列的聚类树高度,默认为 “50” breaks 用来定义数值和颜色的对应关系,默认为 “NA” border_color 表示热图每个小的单元格边框的颜色,默认为 “NA” cellwidth 表示单个单元格的宽度,默认为 “NA”,即根据窗口自动调整 cellheight 表示单个单元格的高度,默认为 “NA”,即根据窗口自动调整 fontsize 表示热图中字体大小 fontsize_row 表示行名字体大小,默认与fontsize一致 fontsize_col 表示列名字体大小,默认与fontsize一致 fontsize_number 表示热图上显示数字的字体大小 angle_col 表示列标签的角度,可选择 “0”,“45”,“90”,“270”,“315” display_numbers 表示是否在单元格上显示原始数值或按照特殊条件进行区分标记 number_format 表示热图单元格上显示的数据格式,如 “%.2f” 表示两位小数; “%.1e” 表示科学计数法 number_color 表示热图单元格上显示的数据字体颜色 legend 表示是否显示图例,值为TRUE或FALSE annotation_row 表示是否对行进行注释 annotation_col 表示是否对列进行注释 annotation_colors 表示行注释及列注释的颜色 annotation_legend 表示是否显示注释的图例信息 annotation_names_row 表示是否显示行注释的名称 annotation_names_col 表示是否显示列注释的名称 show_rownames 表示是否显示行名 show_colnames 表示是否显示列名 main 表示热图的标题名字 gaps_row 仅在未进行行聚类时使用,表示在行方向上热图的隔断位置,如 gaps_row = c(2, 4)表示在第2与第4列进行隔断 gaps_col 仅在未进行列聚类时使用,表示在列方向上热图的隔断位置,同 gaps_row labels_row 表示使用行标签代替行名 labels_col 表示使用列标签代替列名 filename 表示保存图片的位置及命名 width 表示输出绘制热图的宽度 height 表示输出绘制热图的高度 margins 表示热图距画布的空白距离 好吧,看上去基本上都能用到。制作热图应用的统计学原理就不多说了,我也没研究明白,我们用上面的这些参数能做出好看点的图就行。所以其实pheatmap中也有对应的参数scale来对行或列进行均一化,在pheatmap中设置参数或者多加一行代码做个均一化转换都是一样的。 4. 最终流程代码知道了简化版代码的各行命令和pheatmap各参数作用,稍加一点点修改得到最终流程代码: 12345678910111213141516library("pheatmap")nDEGs <- gene[which(gene$group != "NOT_CHANGE"),]sort_DEGs <- arrange(nDEGs,padj)all_matrix <- mycounts[(sort_DEGs[,1]),]heat_matrix_all <- t(scale(t(all_matrix)))# 要做热图的每列分组,底下两行代码是必须的annotation_col <- data.frame(sample = factor(c(rep("SD",4),rep("LD1",4)))) # 简单粗暴地写一个分组矩阵row.names(annotation_col) <- colnames(heat_matrix_all)pheatmap(heat_matrix_all, color = colorRampPalette(c("blue", "white", "red"))(100), treeheight_col = 30, treeheight_row = 10, show_rownames = FALSE, angle_col = 45, annotation_col = annotation_col, filename = "test.pdf") 保存后的差异基因热图如上所示,左边4列是LD长日照1天组,右边4列是SD短日照对照组。数据不是特别好,但是两组还是能区分开的,至少也比第一次做的顺眼一点了。 标题参数main有个小bug,加了参数在plots区域预览是正常的,但是用filename保存到输出文件如果标题是中文会出错,无法显示中文标题。这个小bug倒是无伤大雅,图在plots区也可以直接保存输出,不是非得要用filname参数指定输出文件输出的。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"转录组数据分析","slug":"转录组数据分析","permalink":"http://www.shelven.com/categories/%E8%BD%AC%E5%BD%95%E7%BB%84%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"}],"tags":[{"name":"R语言","slug":"R语言","permalink":"http://www.shelven.com/tags/R%E8%AF%AD%E8%A8%80/"},{"name":"pheatmap","slug":"pheatmap","permalink":"http://www.shelven.com/tags/pheatmap/"}]},{"title":"获得测序原始数据——初探GEO和SRA数据库","slug":"获得测序原始数据——初探GEO和SRA数据库","date":"2022-05-03T20:02:37.000Z","updated":"2022-12-03T16:24:11.000Z","comments":true,"path":"2022/05/04/a.html","link":"","permalink":"http://www.shelven.com/2022/05/04/a.html","excerpt":"最近在看转录组数据分析的文献,想下载一些原始数据自己跑一跑的,发现自己对于几个高通量测序数据库还是有些不太熟悉。以我现在的经验来看,EBI数据库的原始测序数据最容易获得,可以直接在EBI官网下载需要的fastq格式文件,但是NCBI的SRA数据库下载数据还是有些麻烦的,做个学习笔记记录下。","text":"最近在看转录组数据分析的文献,想下载一些原始数据自己跑一跑的,发现自己对于几个高通量测序数据库还是有些不太熟悉。以我现在的经验来看,EBI数据库的原始测序数据最容易获得,可以直接在EBI官网下载需要的fastq格式文件,但是NCBI的SRA数据库下载数据还是有些麻烦的,做个学习笔记记录下。 初探GEO数据库和SRA数据库1. GEO数据库先上数据库链接,点击这里 GEO数据库全称Gene Expression Omnibus database,是由美国国立生物技术信息中心NCBI创建并维护的基因表达数据库。它创建于2000年,收录了世界各国研究机构提交的大多数高通量基因表达数据,GEO除了二代测序数据,还包含芯片测序、单细胞测序数据。 GEO数据库有四种数据存放类型**GSE数据编号(Series)、GPL数据编号(GEO platforms)、GSM数据编号(Samples)和GDS数据编号(Datasets)**。 一篇文章可以有一个或者多个GSE(Series)数据集,一个GSE里面可以有一个或者多个GSM(Samples)样本,如果做的是基因芯片,那每个数据集也会有自己对应的芯片平台,就是GPL(GEO platforms)。GSE编号一般为作者提交时生成的原始数据编号,后续NCBI中的工作人员会根据研究目的、样品类型等信息归纳整合为一个GDS(Datasets),整理后的数据还会有GEO profile数据,也就是基因在这次实验中的表达数据。我平常文献里看到最多的就是GSE编号,可以直接在GEO数据库的搜索框里输入查看。 我个人比较关注的是tools栏目里的FTP Site功能。ftp是文件传输协议,ftp访问的界面非常干净,如果我只需要下载GEO数据库里的文件,而不关注其他多余信息的话,可以通过这个界面非常快速地找到对应GSE编号下所有作者上传到GEO数据库的文件,目录结构层次一目了然。如下图所示,datasets对应GDS编号;platforms对应GPL编号;samples对应GSM编号;series对应GSE编号,网站还很贴心地给了README.txt里面写了帮助文档,包括怎么用ftp访问等等 在Browse Content这个栏目里,我们也能看到这个数据库的一些概览,比如现在有多少数据集,多少个芯片平台,多少上传的样品等等信息。我翻看了一下两年前jimmy出的教程,那个时候的GEO数据库收录的信息只有现在的一半不到,两年时间这些数据呈井喷式的发展,大数据时代信息发展之快也确实让我挺震惊。 尤其是点开基因芯片的平台,以上传的样品数量对这些芯片平台进行排序,发现两年前独占鳌头的Affymetrix公司的HG-U133(也是最古老的基因芯片)已经被illumina公司高通量测序芯片全面超越了接近两倍,这也意味着高通量测序在这两年发展已经不仅仅是快速发展了,简直是火箭式发展……这也是大势所趋。 GEO数据库的搜索地址也很有规律性,比如只有最后的GSE编号不同,其他网址字段都是一模一样的,这也算是方便搜索的一个没什么用处的小技巧?…… 看点有用的,我们根据文献得到的GSE编号进行搜索,可以看到如下页面,前半部分是作者的一些信息,我们可以获得测序物种、作者的联系方式、发表的文章PMID等等,而我们更需要获取的信息在后半部分: 样本信息这里解释一下,有三种主要的数据类型: SOFT 平台信息芯片中探针与基因的对应关系注释文件,样品单独的表达量,所有信息文件 MINiML XML格式的所有数据,同SOFT文件单格式不同,和HTML格式差不多 TXT 这是样品表达矩阵的数据文件,我的理解是总结类型文件,不如用底下的原始数据 原始数据信息也说一下,这里可能会给原始测序的raw data,也可能不给;有的时候会给raw count表达矩阵,也有的时候不给;样品表达矩阵也是有的时候给的raw count,有的时候给的FPKM(比如上面这种情况)。就…挺离谱的,没有标准,如果我们要跑Deseq2复现作者的结论,通过FPKM得到的表达矩阵还不能用。希望网站能制定点相关标准吧(题外话,不然对于我们这种小白,找不到数据直接痛苦面具) 我个人觉得,这个数据库比较重要的是可以获得样品分组信息和SRA数据库的链接地址,方便我们下载测序原始数据(后面介绍怎么下载)。如果不想跑原始数据,也可以直接拿样品表达矩阵来跑转录组下游分析。直接用表达矩阵就是要当心作者拿漂亮数据坑你。 2. SRA数据库同样的先上数据库链接,点这里 其实从网址上就可以看出端倪,这俩数据库都是NCBI旗下的俩兄弟,都是NCBI一个亲爹亲妈养的。我百度的时候也发现,很多人把这两个数据库混在一起,或者叫GEO/SRA数据库,其实两者还是有区别的。 SRA数据库是三大核酸数据库之一,我之前的笔记也有介绍过(点击这里查看)。我个人的理解是,SRA数据库存放的是原始测序文件,而GEO数据库存放的大部分是经过作者处理以后的数据文件(有的也包括了原始测序文件),相对而言SRA数据库更大也更存粹,而NCBI官方也给了下载SRA数据的小工具——SRA Toolkit。 SRA数据类型包含如下四种,看到前缀知道这是SRA数据库,了解一下就行: Studies 研究课题(前缀为ERP或SRP,包含多个Experiment) Experiments 实验设计(前缀为SRS,包含Sample、DNA source、测序平台和测序数据等信息) Samples 样品信息(前缀为SRX,包含一个或多个Runs) Runs 测序结果集(SSR开头的记录,代表测序仪器所产生的reads) 主要说说数据下载方式前几天也有同学问我sra数据库的原始测序数据怎么下载的,找不到下载方式,看的教程都是NCBI上直接下载的。emmmmmm我的第一反应是这不就是NCBI旗下的子数据库嘛……还能从哪儿下载… 废话不多说,直接看官网给的下载工具——SRA Toolkit 这是一个官方给的小工具合集,提供我们各种操作系统下的安装包,我把linux和windows安装包都下了。安装步骤都是一样的,解压,把bin文件夹路径加到系统环境变量,搞定。windows需要打开cmd命令行运行一次prefetch(下载命令),按照提示输入vdb-config --interactive起到类似激活的作用就行了。linux里甚至都不需要编译(也可以conda安装)。可能有的同学对自己的windows系统不熟悉,不知道怎么改自己的系统环境变量,其实这个比linux改环境变量更容易,百度一下吧= = 我个人更推荐在windows系统安装SRA Toolkit,SRA数据库本身服务器在国外,国内访问下载速度慢到令人发指(个位数Kb/s,甚至直接没有),而类似转录组这种数据,细菌可能还好点,动植物做个10X测序动不动就是几个G十几个G,加上分组和生物学重复动辄几十上百G,那点速度下到天荒地老也下不完。纯命令行的linux系统使用代理服务相对windows系统来说要麻烦一点,说白了,windows系统更容易科学上网,为了下载数据没有别的办法。 我们用的就是SRA Toolkit工具包里的prefetch命令下载原始数据,prefetch有个最大的好处是只要知道SRA数据库的数据类型编号,就可以直接下载对应的原始数据。如果要批量下载,可以将数据编号写入txt文件中再运行prefetch命令,或者直接写个循环语句。所以这里的关键是怎么得到数据编号,比如SRR编号等等。 前面GEO数据库提供了SRA数据库的链接,我们可以直接点开(或者点击原始数据底下的SRA Run Selector): 点击右上角的Send results to Run selector: 我们可以看到,这个项目一共有16个基因组测序数据,难道要16个wget命令一个一个下载麽?不需要,如果要下载的基因组数据数量多肯定不行。我们可以从Accession list里获取不同前缀的各种run数据,后面用prefetch命令结合循环语句,直接一步下载。 把Accession list的编号全部复制下来,以linux为例运行cat > id,回车后粘贴编号,按ctrl+c退出,这样就生成了一个名为id的文件,里面内容是我们要下载的基因组测序数据编号。 写一个循环语句cat id | while read id ;do prefetch $id &;done ,就可以全部下载了,这里没有指定输出目录,最终所有原始测序数据会输出到根目录下。 最后顺便说一句,下载测序原始数据的方法很多,不仅仅是官方给的这个小工具。还可以用aspera遵循一定的下载格式也可以下载原始数据,或者用最原始的wget简单粗暴直接下载,只是说这些方法都或多或少受到网络限制的影响,prefetch也只是相对稳定一点。前面的笔记我也介绍过爬虫的编程方法,分解网页结构,批量抓取我们需要的信息,这也不失为一种方法。人是活的,不要拘泥于一种思路。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"SRA","slug":"SRA","permalink":"http://www.shelven.com/tags/SRA/"},{"name":"SRA Toolkit","slug":"SRA-Toolkit","permalink":"http://www.shelven.com/tags/SRA-Toolkit/"},{"name":"GEO","slug":"GEO","permalink":"http://www.shelven.com/tags/GEO/"}]},{"title":"简易爬虫程序编程记录——以微博热搜为例","slug":"简易爬虫程序编程记录——以微博热搜为例","date":"2022-05-02T16:05:41.000Z","updated":"2022-12-03T16:23:08.000Z","comments":true,"path":"2022/05/03/a.html","link":"","permalink":"http://www.shelven.com/2022/05/03/a.html","excerpt":"写的这个小爬虫程序主要是应用requests库和lxml包的etree库,简单介绍一下。","text":"写的这个小爬虫程序主要是应用requests库和lxml包的etree库,简单介绍一下。 1. 关于爬虫百度百科对于爬虫的定义是,网络爬虫(又被称为网页蜘蛛、网络机器人,在 FOAF 社区中,经常被称为网页追逐者),是一种按照一定的规则,自动抓取互联网信息的程序或者脚本。另外一些不常使用的名字还有蚂蚁、自动索引、模拟程序或者蠕虫。 我们只需要知道爬虫的作用是抓取网页的信息,其实现在互联网上充斥着大量的爬虫,包括不局限于火车票抢票软件,各种实时数据分析网站等等,本质上都是发起大量的http请求获得信息。爬虫技术的滥用会导致目标网站在短时间内收到大量的访问请求,进而导致服务器瘫痪,相当于是ddos攻击了。但是爬虫的便利性是不可否认的,尤其是批量操作数据和获取信息,比如批量下载我们需要的文献等等。犯罪的永远是凶手而不是工具,我们在合法的范围内应用好工具,能为我们生活提供非常大的便利。 知其然知其所以然,了解这个技术的最好方法是自己去学,因此写了这个小爬虫程序。为什么拿微博热搜来练手呢,因为微博热搜网页结构非常简单明了,很容易上手…… 1.1 requestsrequests是最常用的Python HTTP客户端库,编写爬虫和测试服务器响应数据时经常会用到,专门用于发送HTTP请求。说白了requests最大的作用就是发起http请求,返回我们需要的网页数据,所谓爬虫就是从网页上抓取和整理我们需要的公开的信息,对于非公开的信息抓取是违法的。 requests请求方式: 1234567requests.get(url, kwargs): 发送GET请求requests.post(url, kwargs): 发送POST请求requests.put(url, kwargs): 发送PUT请求requests.delete(url, kwargs): 发送DELETE请求requests.head(url, kwargs): 发送head请求erquests.options(url, kwargs): 发送options请求这些请求方法的参数和用法一致,必选参数为url,其他参数为可选参数 1.2 etreelxml的etree是从上面requests返回的html源码中提取信息用的,我们可以通过xpath解析网页的dom树,从中获取我们需要的元素和内容。 主要用的也就是etree.HTML(),可以用来解析字符串格式的html文档对象,更方便对我们需要的元素和对象进行抓取,后面演示会说到。 2. 代码和结果展示123456789101112131415161718192021import requestsfrom lxml import etreeimport timeurl = 'https://s.weibo.com/top/summary/'header = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36','cookie': "UOR=www.baidu.com,s.weibo.com,www.baidu.com; SINAGLOBAL=2417808258422.6777.1651037395174; _s_tentry=-; Apache=9947874618898.105.1651493077297; ULV=1651493077318:2:1:1:9947874618898.105.1651493077297:1651037395190; PC_TOKEN=04cd3c070b; login_sid_t=80fe8e3820060c4330191a42b71357dd; cross_origin_proto=SSL; ALF=1683032497; SSOLoginState=1651496497; SUB=_2A25Pa6ZiDeRhGeFL6lAT-CzMyj-IHXVsAJCqrDV8PUNbmtB-LUjdkW9NQm3k0nVgyW6LFmyhR5luy-dtvVNK1VjC; SUBP=0033WrSXqPxfM725Ws9jqgMF55529P9D9WWTcwdUO8qo5ZVwP9-2e8C.5JpX5KzhUgL.FoMfeKzE1hz7eKe2dJLoIp7LxKML1KBLBKnLxKqL1hnLBoMNSK2EeonEeh20"}resp = requests.get(url, headers=header)resp1 = resp.content.decode(encoding='utf-8')resp2 = etree.HTML(resp1)title = resp2.xpath('//*[@id="pl_top_realtimehot"]/table/tbody/tr/td/a/text()')clout = resp2.xpath('//*[@id="pl_top_realtimehot"]/table/tbody/tr/td/span/text()')addresses = resp2.xpath('//*[@id="pl_top_realtimehot"]/table/tbody/tr/td/a/@href')print(time.strftime("%F,%R")+'\\n50条实时微博热搜\\n'+'\\n排列方式:序号+关键词+热度\\n')for i in range(51): if i == 0: print(''.join('置顶'+'\\t'+title[i]+'\\n'+'https://s.weibo.com'+addresses[i]), '\\n') else: print(''.join(str(i)+'\\t'+title[i]+'\\t'+clout[i-1]+'\\n'+'https://s.weibo.com'+addresses[i]), '\\n') 未对代码进行封装,源代码就这么十几行,实现的结果是,执行一次就在当前终端屏幕上输出实时的50条微博热搜话题,并显示序号和热度,每条热搜话题下一行生成微博超链接。 3. 代码详解建立python脚本,导入模块这步不解释了。 url是我们要抓取信息的网站地址,这个很好理解。header是我们调用requests模块需要的一个重要参数,里面提供了我们访问需要的认证信息cookie,http请求本身是无状态的,网站无法确认前一次发出请求的人和后一次发出请求的人是否为同一人,因此需要让网站记住我们的登录信息cookie以响应我们的请求。没有header可能无法返回网页信息,那这一大堆东西是怎么来的呢?需要我们审查网页元素。 3.1 获得cookie和user-agent打开微博热搜首页,登录微博,随便什么空白的地方右键,点击检查,找到network(网络)。 上面的为网页元素,日志控制台,网络,资源,性能和内存等等标签,下面的就是对应的内容,往往点击第一个总结类的文件可以获得request headers信息,这里面最重要的两个信息:cookie和User-Agent 将cookie和user-agent内容全部写到header变量中,这样每次访问网站就带上了我们唯一的标志信息 resp = requests.get(url, headers=header) 访问目标网址,返回的html源码赋值给resp,然而我们看不到返回的值是怎么样的,什么类型的,这里我就要介绍一下vscode的AREPL插件了。 3.2 AREPL查看变量和审查网站元素前面介绍vscode插件说过,AREPL可以实时打印出当前的变量信息而不需要运行代码,极大地方便了我们查看返回的值和信息,知道每一行代码发挥了什么作用。 我们看一下自定义的resp变量是什么: status_code值为200,很明显成功返回了html源码信息,但是点开来看却得不到我们需要的网页文字信息,因为还没有进行解码。我们可以看到编码方式是UTF-8,自然而然的,我们就要对resp变量值进行对应的UTF-8解码,也就是后面的代码resp1 = resp.content.decode(encoding='utf-8'),这里注意一点要用content不能用text 再来点开看看解码后的resp1: 如果有点html基础的话会发现,怎么样,是不是很熟悉!没错!这就是我们在审查网页元素获得的网页的前端结构,这里包括了所有的网页信息,再也不用点开原网站一个一个元素去找啦!(就比如我这小破站的网页元素看地我脑瓜子嗡嗡的)这里可以很轻易地看到各个节点信息,极大方便了我写上面的爬虫代码。 这里我需要三个信息,热搜的标题、热度和网址,我们来展开看一看网页结构: 像洋葱一样一层一层拨开网页结构,我们可以清楚地看到table/tbody/tr/td/a节点的内容是微博热搜标题,a这个节点的标签href就是网址,table/tbody/tr/td/span节点的内容就是热度,至此,网页结构一清二楚,我们要做的就是把信息提取出来,提取的方式就是etree解析这个字符串格式的html文档,生成对应的元素路径。 3.3 解析html文档resp2 = etree.HTML(resp1)就是用来解析字符串格式的HTML文档对象的,将传进去的字符串转变成元素对象 转换后的resp2如下,我们可以看到每个节点都被转换成了_Element对象: 接下来就是顺理成章地用xpath寻找元素路径,将对应内容提取出来,我的元素路径中应用了正则表达式,这里也不解释了。 最后可以将提取出来的三个信息一一打印出来看看是否有问题(AREPL插件真的立大功),有了信息接下来就是整理和排版,那就是print函数和循环语句的基本用法了。虽然python中的print函数和循环语句与R或者linux中略有不同,这个基础知识这里也不再赘述。 唯一我觉得需要注意的是,range() 函数提供的是0-51的整数;官网置顶的微博没有热度显示,所以我写了一个if判断语句区别;排版的时候注意转义字符,其他都是细节微调部分,怎么美观怎么顺眼怎么来。 4. 总结因为这个网页没有做反爬(或许是我没注意到)手段,至少我获取这些公开信息还是没有遇到阻碍的,这也是最最简单的一个爬虫脚本了,调用第三方模块,解析网页,最后提取信息和整理,就是这么简单也很好理解。 我最近还接触到一个明日方舟抽卡记录汇总的小程序,我看了下小程序的方法,猜测这类程序也是类似的爬虫程序。先登录官方网站,需要你输入一个网址,提供token_by_cookie这个值,这个值在network标签中能找到,并且能发现resquest url就是获取token_by_cookie值的网站。 获得这个token之后,我们可以看到抽卡记录可以通过另一个需要token的网址中直接获取,明日方舟的抽卡记录只能保存10页,因此,只需要输入token,直接更改page值1-10,就能获得详尽的抽卡信息。而做成好看的图表也无非是把爬虫程序和作图程序结合一下,封装,最后在小程序调用,思路就是这样的。 同时也能发现,如果我们提供token值给别人,除了抽卡记录以外,还能提取我们的充值信息和源石消费信息等等这类隐私信息。要掌握隐私信息无非是程序的作者想不想做的问题,毕竟还是把隐私信息握在自己手里比较好。 学习就是学习这些技术的思路并为自己所用。","categories":[{"name":"编程自学","slug":"编程自学","permalink":"http://www.shelven.com/categories/%E7%BC%96%E7%A8%8B%E8%87%AA%E5%AD%A6/"}],"tags":[{"name":"python","slug":"python","permalink":"http://www.shelven.com/tags/python/"},{"name":"爬虫","slug":"爬虫","permalink":"http://www.shelven.com/tags/%E7%88%AC%E8%99%AB/"}]},{"title":"vscode远程连接和快速搭建python环境","slug":"vscode快速搭建python环境","date":"2022-04-29T10:17:01.000Z","updated":"2022-12-03T16:17:35.000Z","comments":true,"path":"2022/04/29/a.html","link":"","permalink":"http://www.shelven.com/2022/04/29/a.html","excerpt":"最近在自学python,刚入门苦于不知道从何下手,也不知道用什么编辑器比较适合。在度娘上搜了十几款编辑器,最终决定用微软的vscode,这个编辑器可以配置Python、Java、C ++等编程环境,而且有非常强大的插件功能,界面看着也挺友好,写个日志记录下自己瞎捣鼓的配置。","text":"最近在自学python,刚入门苦于不知道从何下手,也不知道用什么编辑器比较适合。在度娘上搜了十几款编辑器,最终决定用微软的vscode,这个编辑器可以配置Python、Java、C ++等编程环境,而且有非常强大的插件功能,界面看着也挺友好,写个日志记录下自己瞎捣鼓的配置。 本来是想在我的云服务器上装vscode,但是我的云服务器上没有可视化界面……于是在我的小破笔记本上安装了vscode,后来又发现有一个插件可以ssh连接上服务器,只要能ssh连接就可以直接调用服务器上事先安装好的各种python库,真香~ 从头开始记录下使用方法和自己的设置 1. 下载vscodevscode可以直接上官网下载(速度很慢,建议科学上网),选择自己的操作系统,我用的windows 一直下一步就可以了,唯一需要注意的是把vscode加入系统环境变量中(默认选项),安装以后能够在cmd命令行通过code打开说明就改了环境变量。当然也可以在系统环境变量的path中找到,这里不赘述 2. 插件下载2.1 中文语言包英文界面对于我这种小白太难了,所以打开软件第一件事就是安装中文插件,这个在左边拓展栏输入chinese直接可以找到(我这里已经装好了,只是演示记录一下) 2.2 ssh连接插件因为我要远程调用服务器上的python库,所以我首先下载了SSH连接插件 安装以后点击右侧菜单栏的远程资源管理器,可以新建一个远程连接 按照正上方弹出的窗口提示,我们输入自己的用户账号和host地址,之后选择第一个选项,这样我们要连接的远程主机地址就被记录下来了。连接之后输入密码即可远程登录,每次登录都需要输入密码 连接以后可以选择打开文件夹,把根目录文件夹打开就可以调用远程服务器的所有文件了 看到终端成功显示欢迎界面,说明远程登陆成功,终端可以输入和执行命令了 2.3 python拓展插件远程登录只是第一步,接下来安装插件都是远程登录的窗口,安装在本地的插件一般不能用在远程登录窗口。我要搭建python环境,也是先安装python的拓展插件 2.4 AREPL插件这个插件可以在右上角点开,实时打印出你写的python脚本运行结果,变量的赋值等等,还可以检查你写的脚本哪里出错而不需要直接运行代码。这个插件在写爬虫脚本的时候真的非常方便(后面的笔记再分享,写个简单的爬虫脚本就能体会),再也不需要点开网页审查各个元素了,直接在右边框里找到 举个栗子比如我写了个打印皮卡丘的python脚本(滑稽),我不用运行程序就能在右边看到代码运行的结果 3. 脚本调试下载完插件,写完代码,我们首先要进行的就是代码调试,点击左侧菜单栏的 运行和调试 ,我们直接点击创建 launch.json文件 在弹出的正上方菜单栏选择第一个调试配置打开的python文件 本质上就是生成一个调试的json文件,不用太多了解,调试当前打开的文件就可以。也可以根据自己需要改成只调试指定名称的python脚本,改的就是红框里的部分 在脚本页面直接按F5就可以运行了,可以在代码的行号前设置红色的断点,用来分段测试代码,在写的代码比较多需要一段段检查错误的时候会比较有用。比如我在上面的皮卡丘脚本的第12行设置一个断点,再按F5运行脚本,就会从第一行报错(因为print函数被断点隔开了),右边是AREPL插件的输出结果,不受代码运行与否和断点的影响,所以能正常显示 我们同时也能看到右下角是bug调试区,也就是说我们在调试区运行程序,会自动给我们分配一个debug控制区的终端,我也可以同时在上面的bash区终端运行别的程序。 4. 格式化文档这几天自学过程中我也发现,python代码是有严格的缩进要求的,不像是linux系统中的shell语言,一行写完可以在另一行随便插几个制表符继续写下一个命令。python严格按照冒号和缩进来区分代码块之间的层次,在 Python 中,对于类定义、函数定义、流程控制语句、异常处理语句等,行尾的冒号和下一行的缩进,表示下一个代码块的开始,而缩进的结束则表示此代码块的结束。哪怕有一个多余的缩进量,就会系统报错 在赋值前后,运算符前后,#号注释之后等一些不用区分代码块层次的地方,python对空格要求却不是那么严格。虽然要求不严格,但是不小心手滑多打了或者少打了空格总归影响美观(我真的有强迫症),这个时候可以用右键的格式化文档功能,一键自动改成标准格式 比如上面这个丑不拉几的程序虽然能跑出来结果,但是强迫症看完会当场去世。这个时候可以用右键的格式化文档功能一键对齐,如下 如果点格式化文档显示的是要装autopep8拓展,那就点确认安装。这个功能就是靠autopep8这个软件实现的,但是这个拓展软件是靠pip安装的,有的人没有安装pip,或者有的人(比如我)pip有问题,一直显示ssl证书不能获取拒绝安装,改pip源也无法解决问题(又是套娃解决问题的一天),就要去github下载autopep8本地安装,更改环境变量才可以使用。也可以用conda直接一步安装,不得不说conda管理python环境变量是真的香 格式化文档也不是万能的,比方说我在父目录下封装了一个函数,我想在子目录下调用,vscode有一个缺点就是需要把当前目录加到环境变量里,然后才能调用我封装好的函数,但是!添加环境变量之后再调用,这个时候运行格式化文档的功能,系统会自动把调用模块排在添加环境变量步骤之前,因为软件的设计就是把调用模块一定放在第一位。 打个比方,我在父目录demo下封装了printpikaqiu()这个函数,这个函数作用是打印皮卡丘。文件目录如下 然后我在子目录test下调用,就需要先拓展环境变量再调用,代码如下图,f5运行没毛病,打印出一只皮卡丘: 但是右键格式化文档之后,代码直接变了,再运行直接红色的报错跳脸上 因为代码顺序改了,没有添加环境变量,找不到父目录demo怎么可能调用pikaqiu模块呢?这就非常尴尬了 因此在调用自己封装的函数和模块的时候,不要用格式化文档。现在暂时还没找到可靠的解决方法 5. 其他设置vscode主菜单栏 文件—首选项 底下有非常多非常详细的设置选项,而且可以不同设备进行同步,这个是其优点之一。并且可以通过ctrl + shift + p 快速打开设置,支持直接修改配置的json文件,这个暂时还没用到,我只是改了个字体大小,以后有重要修改的时候再做记录,方便后续查看。 顺便提一嘴,我的云服务器是安装了anaconda管理python环境的,用vscode远程ssh登录云服务器后,仍然可以在右下角选择我用anaconda安装的各个版本python,依然是一键切换环境,真的太方便了。之前没用编辑器码代码,每次都要在linux里通过vim码好保存退出,再运行。。。各种意义上的身心折磨。。。 现在就一个字,香!","categories":[{"name":"编程自学","slug":"编程自学","permalink":"http://www.shelven.com/categories/%E7%BC%96%E7%A8%8B%E8%87%AA%E5%AD%A6/"}],"tags":[{"name":"vscode","slug":"vscode","permalink":"http://www.shelven.com/tags/vscode/"},{"name":"python","slug":"python","permalink":"http://www.shelven.com/tags/python/"}]},{"title":"转录组数据分析笔记(8)——ggplot2和ggrepel绘制火山图","slug":"转录组数据分析笔记(8)——ggplot2和ggrepel绘制火山图","date":"2022-04-24T20:53:27.000Z","updated":"2022-12-03T16:32:02.000Z","comments":true,"path":"2022/04/25/a.html","link":"","permalink":"http://www.shelven.com/2022/04/25/a.html","excerpt":"主要介绍下在用DESeq2得到我们想要的差异表达基因后,如何在R中用ggplot和ggrepel绘制火山图。","text":"主要介绍下在用DESeq2得到我们想要的差异表达基因后,如何在R中用ggplot和ggrepel绘制火山图。 1. 前言前面介绍了怎么用DESeq2做两组样本的差异基因表达分析,以及怎么用dplyr包给DESeq2运行结果增加一列分组信息,我们先看下两个R包运行结束后生成的gene_0_1.csv文件是怎么样的: A-G列结果是DESeq2跑的,我们用到的只有基因名,log2FoldChange和padj这三列,通过log2FoldChange绝对值大于1,调整后的pvalue也就是padj(即FDR值)小于0.05筛选除差异表达基因,最后加上group列方便查看。 到这里已经可以通过排序找到我们感兴趣的基因了,但是这样的数据不够直观,我们还可以用最著名的绘图R包ggplot2做个火山图。这里需要准备的绘图R包是ggplot2,还有添加标签的R包ggrepel。 1.2 两个注意点什么是火山图就不多bb了,重要的是知道我们可以从火山图获得两个信息:差异表达倍数(FoldChange值)和统计学显著性的标志p值。为了更方便比较和作图,我们一般用log2FC代替Fold Change值并作为X轴数据,表示两样品(组)间表达量的比值,对其取以2为底的对数即为log2FC,一般默认取log2FC绝对值大于1为差异基因的筛选标准;用FDR(也就是padj值)代替pvalue,并取-log10(FDR)值作为Y轴,FDR是错误发现率,是pvalue值进行校正得到的。 log2FC有正有负很好理解,可能有同学发现,有的基因明明有pvalue值,但是校正之后的FDR值却是NA,如下: 查阅了一些资料,当基因的在所有样本中表达量为0,则两个值都为NA;当read count数较低时,DESeq2进行Independent Filtering过滤了一部分可能造成假阳性的结果,此时padj值为NA。因此,这部分数据在做火山图的时候因为没有对应的Y值也会被过滤掉。 2. 流程代码这部分需要一点R语言基础,需要知道怎么改自己的参数。假设前面没有用dplyr包做差异筛选(做了也不影响,只是多一列数据而已)只用DESeq2跑了个结果,我们同样可以用ggplot2包做筛选,用ggrepel包美化做标签。继续用前面DESeq2生成的csv文件,流程代码如下: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748library("ggplot2")gene <- read.csv("gene_0_1.csv", stringsAsFactors = FALSE)gene[which(gene$log2FoldChange <= -1 & gene$padj < 0.05), "GROUP"] <- "DOWN"gene[which(gene$log2FoldChange >= 1 & gene$padj < 0.05), "GROUP"] <- "UP"gene[which(abs(gene$log2FoldChange) < 1 | gene$padj >= 0.05), "GROUP"] <- "NOT CHANGE" # |代表或,和linux里的管道是完全不一样的。以上三步新建了一列GROUP,筛选并赋予了三个值。res <- ggplot(gene, # ggplot数据来源,这里省略了data = 和mapping = aes(x = log2FoldChange, # 表示映射关系,就是定义xy y = -log10(padj), col = GROUP)) + # 注意这里定义颜色用col,以GROUP值区分 geom_point(alpha = 0.5, # ggplot做散点图,设置点透明度和大小 size = 1) + scale_color_manual(values = c("red","blue","grey"), limits = c("UP","DOWN","NOT CHANGE")) + # 自定义颜色 theme(panel.grid = element_blank(), # 去网格线 panel.background = element_rect(color = "black", fill = "transparent"), # 去背景色,透明 plot.title = element_text(hjust = 0.5), # 调整图标标题位置为中间 legend.key = element_rect(fill = "transparent"), legend.background = element_rect(fill = "transparent"), legend.position = "right") + # 设置legend图标 geom_vline(xintercept = c(-1, 1), color = "gray", size = 0.3) + # 设置x轴辅助线 geom_hline(yintercept = -log10(0.05), color = "gray", size = 0.3) + # 设置y轴辅助线 labs(x = "log2 Fold Change", y = "-log10(FDR) ", title = "LD 1 day vs LD 0 day") # 设置坐标轴标题和火山图标题res # 查看结果,plots中可以查看library("ggrepel")up <- subset(gene, GROUP == "UP") # subset从数据框中筛选符合条件的数据up <- up[order(up$padj), ][1:5, ] # order升序排序,取前5个down <- subset(gene, GROUP == "DOWN")down <- down[order(down$padj), ][1:5, ]resdata <- res + geom_text_repel(data = rbind(up, down), # ggrepel特有的函数 aes(x = log2FoldChange, y = -log10(padj), label = X ), # label值指定哪列做标签 size = 3, box.padding = unit(0.5, "lines"), segment.color = "#cccccc", show.legend = FALSE) # 以上都是特有参数resdata # 查看结果ggsave("gene_0_1.png", resdata, width = 10, height = 10) # 输出结果文件 3. 代码详解3.1 ggplot2123gene[which(gene$log2FoldChange <= -1 & gene$padj < 0.05), "GROUP"] <- "DOWN"gene[which(gene$log2FoldChange >= 1 & gene$padj < 0.05), "GROUP"] <- "UP"gene[which(abs(gene$log2FoldChange) < 1 | gene$padj >= 0.05), "GROUP"] <- "NOT CHANGE" 之前这里稍微解释一下,即使前面没有用dplyr包,用别的方法同样可以筛选差异基因并且新增一列分组数据,万变不离其宗,核心的判断方式是一样的。如果前面学了linux,注意最后 | 这个符号在R里表示或,不要和管道混淆。 123456ggplot(data = 输入的数据, mapping = aes(x = 定义值, y = 定义值, 其他参数)) + genom_point(参数) + # 选择作图方法和参数 其他设置函数和参数 # 可有可无,美观相关的东西 我总结了一下ggplot最基本的结构,data和mapping是缺省值,可以不写。 输入的数据可以是表格,可以是数据框等等;aes自定义点的映射范围,大小,颜色等等;作图方法有很多,比如点状图是genom_point。自由度很高,能设置的东西也非常之多,只有两点需要注意,同一个函数不同参数用 , 隔开;不同函数用 + 隔开。 中间的设置函数也稍微解释一下: scale_color_manual() 该函数是R中的一种自定义配色方法,手动把颜色赋值给参数value。我这里将UP的点赋予了红色,DOWN的点赋予蓝色,其他点赋予灰色。 theme() 该函数与主题配置有关,参数非常多,可以选取需要的比如背景色、网格线等等进行设置。这里举个例子,legend是图标,在ggplot中legend有四部分: legend.tittle, legend.text, legend.key和legend.backgroud,而每一个部分都有四种函数可以嵌套(也是是对应4种处理方式):element_text()绘制标题相关;element_rect()绘制背景相关;element_blank()空主题,对元素不分配绘图空间;element_get()得到当前主题的设置。每个函数还有相应的参数,说起来就没完没了了。。。常用的设置知道就行。 geom_vline() 和 geom_hline() 这两个函数分别设置x轴y轴辅助线,目的是使我的火山图更直观,从图上可以直接看到我的分类依据。 labs() 该函数自定义x轴y轴标签和图标标题。这里提一嘴,火山图标题也是一个注意点,一般是 处理组vs对照组 ,因此前面也说到DESeq2处理数据要注意顺序问题,不然会得出完全相反的结论,在火山图上的表现为与实际火山图呈镜像对称,这也很好理解。 这里看一下res结果,我们可以在plots中看到缩略的预览图: 3.3 ggrepel在过滤掉4000多个FDR值为NA的点,我们获得了一个不怎么像火山的火山图 (简直丑爆了) ,但是数据还是挺美丽的,上调区域和下调区域都有前后对比差异非常大的基因:log2FC绝对值越大,差异越明显;-log10(FDR)值越大,可信度越高。 但是这个图还有个缺点,我不知道这些代表性的差异点对应什么基因名,我还要回到excel里去筛选排序。因此,我推荐用ggrepel包对火山图进一步美化,加上基因标签,能一眼看到我感兴趣的基因。 这个包的原理和发展咱就不说了,已经是半夜4点了。。。简单介绍下中间用到的函数的结构。 subset() 从数据框中筛选符合条件的数据,我将UP的点和DOWN的点都提取出来。 order() order是升序排序,因为上调和下调的基因都比较多,全部打上基因名标签是不现实也没有意义的。我按照padj列也就是FDR值进行升序排序,取前5个可信度最高的基因打上基因名标签。当基因较少的时候是可以全部打上标签的。 在前面ggplot2作图的基础上,我们加上geom_text_repel()这个特殊的ggreple包函数,这个函数是基于函数geom_label()做的改良,它将标签置于一个方框中,并且每个标签有算法优化不会重叠。该函数的结构与前面的ggplot前半部分类似: 12345geom_text_repel(data = 输入数据, aes(x = 定义值, y = 定义值, label = X ), 其他参数 这里所有参数设置都是平行的,所以只需要 , 隔开。 我之前说到提取了up和down的数据,这里我把它们rbind一下合在一起,就形成了新的数据框数据,也就是我只对前面排序筛选的上调下调各5个基因打标签。这里注意下aes()这里的 label 是指定标签的,也就是我们这里的基因名,应该用的行名才能和数据一一对应,这里我用X是偶然发现的一个很有趣的事: 前面导入gene数据框的时候,自动把行名加到了第一列成了单独的一列,且该列列名系统定义为X,我们可以进入environment找到gene点开看看这个数据框结构,如下所示: 因此这里直接用label = X就能完美解决问题。反而我回头用row.names = 1用第一列做行名修改了gene数据框的读取方式,再在这里用label = rowname(gene)会提示长度错误或者不匹配,个中原因我暂时还没想明白。 其他特有参数就解释一下我用到的几个: size: 标签大小 box.padding: 标签连接方式,我用了线 segment.color: 线段颜色,可以用RGB颜色代码 show.legend: 是否显示标签的标签 =_=好像有点绕,说白了就是要不要再图标上再打标签… 同样放一张plots里的缩略图,是不是要直观一点了呢? 3.4 结果输出ggsave()是ggplot2包里的输出结果的函数,自定义输出的文件类型,比如pdf、png等等,还可以自定义输出图片大小,这里不赘述,主要放一个完成图看看和plots里的缩略图做个比较。完成图如下: 可能还是有些不美观,但是这些数据很不错,极端点偏离X轴和Y轴较远,都是我们需要重点关注的基因。 如果我们记下了这几个极端点,我们还可以通过在ggplot中限制X轴和Y轴范围比如xlim(-10, 10) + ylim(0, 14),再次缩小范围,得到一个更像火山的火山图 (没有意义,纯粹吃饱了撑的) 如下:","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"转录组数据分析","slug":"转录组数据分析","permalink":"http://www.shelven.com/categories/%E8%BD%AC%E5%BD%95%E7%BB%84%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"}],"tags":[{"name":"R语言","slug":"R语言","permalink":"http://www.shelven.com/tags/R%E8%AF%AD%E8%A8%80/"},{"name":"ggplot2","slug":"ggplot2","permalink":"http://www.shelven.com/tags/ggplot2/"},{"name":"ggrepel","slug":"ggrepel","permalink":"http://www.shelven.com/tags/ggrepel/"}]},{"title":"linux操作指令总结整理","slug":"linux操作指令总结整理","date":"2022-04-20T15:44:49.000Z","updated":"2022-12-03T16:10:22.000Z","comments":true,"path":"2022/04/20/b.html","link":"","permalink":"http://www.shelven.com/2022/04/20/b.html","excerpt":"该篇内容非常之多,主要记录自己能用的上的linux操作指令和自己的一些理解,想要用的时候方便站内搜索直接查找。","text":"该篇内容非常之多,主要记录自己能用的上的linux操作指令和自己的一些理解,想要用的时候方便站内搜索直接查找。 1. linux常用命令cdcd:Change directory修改(进入)工作目录,只对目录文件有效 12345cd / 进入根(root)目录cd - 返回上次的目录cd 返回家(home)目录cd ~ 返回家目录cd .. 返回上一级目录 ls ls:List filesls计算不了目录内文件大小,所以显示的目录大小不是实际的,要看目录实际大小用du命令 123456-a 列出包括.a开头的隐藏文件的所有文件-A 通-a,但不列出"."和".."-l 列出文件的详细信息-c 根据ctime排序显示-t 根据文件修改时间排序-h 将文件大小按照易于读懂的方式显示(多少M,多少G) ll和ls-l是同样的用法,linux可用,mac中不能用,可以改环境变量文件自定义ll用法 pwdpwd:print working directory,打印当前所在目录 cpcp: Copy file拷贝并粘贴文件,并且可以重命名 12345-b 覆盖前做备份-f 如存在不询问而强制覆盖-i 如存在则询问是否覆盖-u 较新才覆盖-t 将多个源文件移动到统一目录下,目录参数在前,文件参数在后 $ cp ../data/xist.fa xist_seq.fa # 复制上一个目录data目录下的xist.fa到当前目录,并重命名为xist_seq.fa$ cp -r 003/ 007 # 递归的方式,复制003目录到007目录,目录复制到目录要用递归 mvmv: Move file移动文件,相当于windows下的剪切粘贴,如果剪切粘贴到同一目录下,则为重命名 12345-b 覆盖前做备份-f 如存在不询问而强制覆盖-i 如存在则询问是否覆盖-u 较新才覆盖-t 将多个源文件移动到统一目录下,目录参数在前,文件参数在后 $ mv a1.index.sh ../ # 移动到上一目录$ mv a1.index.sh a2.index.sh # 重命名为a2.index.sh$ rename txt doc * # 把所有txt改成doc,批量文件重命名可以用rename rmrm: Remove file删除文件 1234-r 删除文件夹(就是删除目录)-f 删除不提示-i 删除提示-v 详细显示进行步骤 一定要慎重使用,命令行模式下删除文件不可恢复$ rm -rf *.fna #删除目录下所有以.fna结尾的文件 lnln: Link files创建连接文件,包括软连接和硬链接,一般软连接比较常用,相当于windows下的快捷方式;硬链接相当于重要文件的备份,默认硬链接删除原文件,硬链接文件不受影响,软连接文件则无效 12-s 建立软连接 -v 显示详细的处理过程 mkdirmkdir:Make directory创建文件夹(目录) 123-p 递归创建目录,若父目录不存在则依次创建-m 自定义创建目录的权限-v 显示创建目录的详细信息 $ mkdir rnaseq #创建一个名为rnaseq的目录 touch建新的空文件(可写入的文件)$ touch 1.txt 2.txt 3.txt # 同时新建三个文件,一个文件可以直接vim建立 catcat: concatenate 连接cat的一个作用是查看文件,一般是比较小的文件,行数小于一个屏幕,最多不要超过两个屏幕,否则会刷屏(屏幕输出的方式)cat另一个作用是合并多个文件,一般配合重定向合并为一个新文件或者将一个文件内容追加到另一个文件结尾$ cat a1.txt a2.txt >all.txt # 合并文件,并不会删除原文件,覆盖新文件内容,新文件为all.txt$ cat a1.txt >>a2.txt # 同样是合并,a1重定向到a2结尾$ cat >id.txt # 回车输入内容,可新建id.txt文件,ctrl+c退出 echo不可以这样新建,只能echo "内容">id.txt 1-A 显示文件内的空白信息 linux系统下是换行\\n;mac系统下是回车\\r;windows系统下回车加换行两个字符\\r\\n 三者都是空白,用less无法看出区别,只能在cat -A下看到不同操作系统的换行符信息 less / moreless和more都是文件查看工具,但是less功能更多一些,在windows系统下打开一个10G的文件比较困难,但是在Linux下非常方便,less可以打开非常大的文件,压缩格式也可以直接打开。注意后面接文件,不能接目录。 12-m 显示类似于more命令的百分比-N 显示行号 more:q退出,space向下翻一页,enter向下滚动一行,b往前翻一页,会加载全部显示浏览到百分之几,退出后会加载显示的所有内容less:类似,还可以用pageup和pagedown,不会加载全部,退出后不会加载文件内容显示到当前界面less下按h进入帮助界面;按/向下搜索字符串,按?向上搜索字符串,搜索状态下n和p前后跳转;按v进入编辑 head / tail这两个命令比较简单,只是取一个文件的头部和尾部多少行,默认10行,可以加-n进行设置,利用管道可以取文件中间行$ head -40 a.txt | tail -20 #取文件第21~40行$ tail -n +20 notes.log #取文件的第20行到文件末尾 g(un)zip/ b(un)zip2gzip和bzip2是文件压缩工具,默认直接对源文件进行处理,压缩比率在2/3左右,都可以进行设置加上un,为unpack的意思,表示解压缩linux压缩文件格式是.gz和.bz2windows压缩文件有.rar文件,可以下载rarlinux工具解压缩;.zip文件可以通过unzip命令解压bzip2压缩比更高(尽量下载bz2压缩文件),但是占用更多CPU$ gzip a.txt # 压缩a.txt文件$ gunzip a.txt.gz # 解压a.txt.gz文件压缩的文件可以用less或者zcat打开文件 tar(很多生物软件是打包并压缩的)tar:Tape archive (磁带档案)tar主要用于打包,由于tar能调用gzip或者bzip2进行压缩,而打包和压缩经常如windows系统一样合并为一个过程 123-c 建立打包档案,可搭配 -v 来察看过程中被打包的档名(filename)-t 察看打包档案的内容含有哪些档名,重点在察看文档名就是了(同less功能)-x 解打包或解压缩的功能,可以搭配 -C (大写) 在特定目录解开 以上三个命令不能同时使用,只能三选一辅选项: 1234-j 透过 bzip2 的支持进行压缩/解压缩:此时档名最好为 *.tar.bz2-z 透过 gzip 的支持进行压缩/解压缩:此时档名最好为 *.tar.gz-v 在压缩/解压缩的过程中,将正在处理的文件名显示出来!-f filename -f 后面要立刻接要被处理的档名!f很重要,每次执行tar命令都要加上 对于初学者,记住c是creat,创建,x是解包,z对应gzip,j对应bzip2即可,所以常用的命令如下:$ tar -jcvf filename.tar.bz2 A B C #打包压缩为bz2结尾文件$ tar -jxvf filename.tar.bz2 # 解压缩.tar.bz2结尾文件$ tar -zcvf filename.tar.gz A B C #打包压缩为gz结尾文件$ tar -zxvf filename.tar.gz # 解压缩.tar.gz 结尾文件$ tar -jxvf filename.tar.bz2 -C 目录名 #解压缩到指定目录,注意是大写Cless命令可以不解压只查看(真的强大),tar -tf filename同样如果只需解压其中一个文档,可以先通过-t查看文档名并复制,再在前面解压缩的命令基础上加空格和文档名 wcwc = Word Count统计一个文件中,行数,单词数(有空格或者换行符的字符串),字符数 1234-l filename 报告行数-c filename 报告字节数-m filename 报告字符数-w filename 报告单词数 统计当前目录下有多少文件$ ll | wc # 注意显示行数比实际多两行,因为还有隐藏的当前目录.和上一层目录.. 可通过ls -a查看 sort排序,默认按第一列排序,可以通过-k进行设置;默认排序规则为按ASCII码排序,可以通过-n进行修改;-r取相反方向; 12345-n 依照数值的大小排序。-o 将排序后的结果存入指定的文件。-r 以相反的顺序来排序。-t 指定排序时所用的栏位分隔字符。-k 选择以哪个区间进行排序。 $ sort -nk2 -k1 01.txt | less # 在01.txt文件中,根据第二列数字大小进行排序,数字一样的比较第一列并排序 uniq用于检查及删除文本文件中重复出现的行列,一般与 sort 命令结合使用,排序之后使用uniq 1234-u 显示未重复的行-c 统计重复行的数量(在行首标注)-ci 忽略大小写统计重复行-d 显示重复出现的行 # cut -f 1 blast.out | sort -t "|" -nk2 | uniq | wc -l #从blast.out文件中提取第一列(f代表字段),第一列字段以“|”分割并比较第二段的数字大小进行排序,去除重复行,并记录行数 即记录有多少条比对上的基因 dfdf: disk freedf用于查看磁盘消耗,显示磁盘可用空间数目信息及空间结点信息。一般加一个-h选项,然后接要查看的磁盘,默认所有磁盘。 12345-a 显示全部文件系统-h 文件大小友好显示-l 只显示本地文件系统-i 显示inode信息-T 显示文件系统类型 dudu: Disk usagedf用于查看磁盘使用情况,du用于查看目录所占磁盘大小,一般也加-h选项 12-h 方便阅读的方式(显示带单位)-s 只显示总和的大小 findfind顾名思义,主要用于查找文件。因为当文件越来越多的时候,由于Linux是文本界面,不方便可视化文件,这个时候就可以利用find快速找到需要的文件。find支持多种搜索方式主要用的搜索方式:find 目录 Expression 条件$ find /media/ -name *.fna #查找media目录下所有.fna结尾的文件$ find /media/ -size 100M #查找media目录下所有大于100M的文件 which$ which filename # 查看可执行文件的位置,在PATH变量指定的路径中查看系统命令是否存在及其位置 whereis该指令只能用于查找二进制文件、源代码文件和man手册页,一般文件的定位需使用locate命令 locate是find -name的另一种写法,但是要比后者快得多,原因在于它不搜索具体目录,而是搜索一个数据库/var/lib/locatedb,这个数据库中含有本地所有文件信息。Linux系统自动创建这个数据库,并且每天自动更新一次,所以使用locate命令查不到最新变动过的文件。为了避免这种情况,可以在使用locate之前,先使用updatedb命令,手动更新数据库 toptop可以动态显示(3s一次)系统进程使用情况,类似于windows系统的任务管理器。可以显示当前系统正在执行的进程的相关信息,包括进程ID、内存占用率、CPU占用率等。 psps: process statusps也是系统进程管理工具,与top不同的是,top可以动态显示,而ps则是静态显示,是某一时刻的快照,静态显示的好处是便于其他程序捕获结果,进行处理。 killkill的作用是杀死进程,给定一个任务的PID号,可以通过top或者ps命令获得,例如当前有一个sleep进程,pid号为12000;通过kill -9可以强制杀死$ kill -9 12000 12345671 终端断线2 中断,相当于ctrl+c2 退出,同ctrl+\\9 强制终止15 终止进程,默认为1518 继续,与STOP相反,fg/bg命令19 暂停,同ctrl+z chmodchmod: Change mode用于修改文件权限,Linux基础权限可以包括ugo模式(文字设定法)以及421模式(数字设定法),可以用通配符一次修改所有类型的文件文字设定法:u表示属主(user),g表示同组群用户(group),o表示其他用户(other),a表示所有用户(all) 123456+ 添加权限- 删除权限= 赋予给定权限,并取消其他所有权限r 可读(read)w 可写(write)x 可执行(execute) 数字设定法: 12340表示没有权限,1表示可执行权限,2表示可写权限,4表示可读权限7:可读可写可执行 4+2+16:可读可写 4+25:可读可执行4+1 $ chmod 721 a1.index.sh # 421模式修改与之类似的还有chown与chgrp,这两个权限更大,需要root权限;chown: Change owner$ chown 用户名 目录名/ # 修改目录的属主chgrp: Change group$ chgrp 组名 目录名/ # 修改目录的组名 exit退出登录,exit是正确退出,最好不要直接点windows关闭窗口按钮退出,也不要使用ctrl+D给定退出信号退出。 man详细解释命令,系统命令可以用这个找,下载的程序往往是–help wget后面接下载网址,可以直接由地址获取下载文件 su:super user获得超级管理员权限,root权限,需要输入密码sudo:super user do暂时取得root权限,配置系统经常能看到sudo yum echo在标准输出(屏幕)上显示文字 12-n 输出之后不换行,去除结尾的换行符。注意默认一行后有一个换行符-e 转义字符按照对应方式处理 yum(centos是yum,ubuntu是apt) Yellow dog Updater Modified是一个软件包管理器,能够从指定的服务器自动下载rpm包进行安装并且自动处理依赖性关系,yum优点提供了查找、安装、删除某一个、一组甚至全部软件包的命令,并且命令简洁便于使用。 1234567891011121314151617181920yum clean all # 清除原有yum缓存yum repolist # 列出仓库信息yum install software # 安装yum update # 更新yum list software # 查看软件yum list all # 查看所有软件yum list installed # 列出已安装软件yum list available # 列出可安装软件yum reinstall software # 重新安装yum remove software # 卸载yum info software # 查看软件信息yum search software # 根据软件信息查找软件yum whatprovides file # 根据文件找出包含此文件的软件yum history # 查看系统中软件管理信息yum history info 数字 # 对该数字为id的信息进行显示yum groups list # 列出软件组 yum groups info # 查看软件组的信息yum groups install sfgroup # 安装软甲组yum groups remove sfgroup # 卸载软件组yum repolist # 查看yum源信息 cut命令从文件的每一行剪切字节、字符和字段并将这些字节、字符和字段写至标准输出如果不指定 File 参数,cut 命令将读取标准输入。必须指定 -b、-c 或 -f 标志之一 1234-b 以字节为单位进行分割。这些字节位置将忽略多字节字符边界,除非也指定了 -n 标志-c 以字符为单位进行分割-d 自定义分隔符,默认为制表符-f 与-d一起使用,指定显示哪个区域 xargs与管道不同,xargs可以给下个命令传递参数。$ ls *.gz | head #只可以输出前10个文件名$ ls *.gz | xargs head #输出.gz结尾的所有文件前10行这里要注意下其实命令是有省略的,完整应该是ls *.gz | xargs -i head{} #传递参数到head的花括号中 jobs查看当前在后台执行的命令,可查看命令进程号码 &运行命令时,在命令末尾加上&可让命令在后台执行 顺便说一下 | ; && ||区别 1234&& 左边命令成功运行了,右边命令才会运行,就是逻辑与的功能; 不管左边命令有没有成功运行,右边命令都会运行,两者之间独立| 左边命令的结果作为右边命令的参数,注意与xargs区分|| 左边运行的命令失败,右边的命令才会运行,否则只显示左边命令运行结果 nohup命令可以使命令永久的执行下去,和终端没有关系,退出终端也不会影响程序的运行; & 是后台运行的意思,但当用户退出的时候,命令自动也跟着退出。 那么,把两个结合起来nohup 命令 &这样就能使命令永久的在后台执行 fg N将命令进程号码为N的命令进程放到前台执行,同%N #注意是进程号不是PID!kill程序需要PID bg N将命令进程号码为N的命令进程放到后台执行 cal 显示日历 date 显示时间 2. 基本操作源码编译安装软件都有Readme文件或者install文件说明安装方式,一般是以下步骤:1、运行configue脚本 #检查系统环境配置情况,缺少哪些东西,缺少的可以yum下载安装2、运行make check命令(可选)3、敲make命令进行编译4、make install命令安装,出现可执行程序 文件校验下载大的文件会附带.md5文件任意长度信息逐位计算,产生128位hash值,不可逆。也就是说MD5算法可以位任何文件产生一个独一无二的数据指纹,通过校验下载前后的MD5值是否发生改变,就可以知道源文件是否被改动$ md5sum filename > data.md5 # 对文件(可多个文件)生成md5校验码(32位,16进制),并命名为data.md5$ md5sum -c data.md5 # 校验文件,如果校验码相同则显示OK 重定向本质是将输出到屏幕的内容重定向到一个新的文件夹中,大于号和小于号都是代表数据的流向$ echo “想要的内容”> 文件名 #覆盖原文件的内容$ echo “想要的内容”>> 文件名 #想要的内容追加到文件后,原文件内容不修改一个>是覆盖,两个>>是追加 Ctrl+C终止并退出前台命令的执行,回到SHELL Ctrl+Z暂停前台命令的执行,将该进程放入后台,回到SHELL 3. vimvim(主要用来写脚本,编辑文件)vim是Linux系统自带的文本编辑器,可以理解成为windows系统下的word软件,适合编辑小文件,会一次加载全部内容到内存 123:w filename 将文件以指定的文件名保存起来 :wq 保存并退出:q! 不保存而强制退出 注意vim是vi的拓展,有些自定义设置要在vim下生效,最好是用vim用户设置优先级高于全局设置,设置文件都在家目录~下设置,且均为点开头的隐藏文件,如下~/.vimrc~/.bashrc 3.1 命令行模式功能键:1)插入模式 123i 切换进入插入模式 insert mode ,按"i"进入插入模式后是从光标当前位置开始输入文件a 进入插入模式后,是从目前光标所在位置的下一个位置开始输入文字o 进入插入模式后,是插入新的一行,从行首开始输入文字 2)从插入模式切换为命令行模式按 ESC 键3)移动光标直接用键盘上的光标来上下左右移动,也可以用小写英文字母h、j、k、l,分别控制光标左、下、上、右移一格。 1234567G 移动到文件末尾,15G移动光标至文章的第15行行首gg 移动到文件开头$ 移动到光标所在行的行尾^ 移动到光标所在行的行首H 光标移动到这个屏幕的最上方那一行的第一个字符M 光标移动到这个屏幕的中央那一行的第一个字符L 光标移动到这个屏幕的最下方那一行的第一个字符 4)删除文字 123x 每按一次,删除光标所在位置的后面一个字符X 大写的X,每按一次,删除光标所在位置的前面一个字符dd 删除光标所在行 1,6d删除1到6行 5)回复上一次操作 1u 如果误执行一个命令,可以回到上一个操作。按多次u可以执行多次回复 6)继续下一个操作 12n或. 比如查找一个字符串以后,继续寻找下一个字符串,按多次n执行多次操作N 与 n 刚好相反,为反向进行前一个搜寻动作 3.2 底线命令模式1234567:/word # 查找word字符串:%s/x/y/gc # 所有x被y替换 g代表全局,c代表交互模式(每次替代会提示):!命令 # 命令先执行,vim被挂起。执行后按enter回到vim:split # 横屏分屏显示 ctrl+ww切换上下屏:vsplit # 纵向分屏:only # 取消分屏:n1,n2s/word1/word2/g # 在第n1与n2行之间寻找word1这个字符串,并将该字符串取代为word2 vim还有专门的键盘图。。。放一个简略版的 4. 基础命令三剑客三剑客的命令非常之多,完全可以出一本书,这里只放一些简单的和我能用得到的 4.1 三剑客之grepgrep(找基因信息比较方便)Global Regular Expression Print,全局正则表达式版本文本搜索工具,类似于正则表达式搜索,可以在一个大的文件中快速搜索到满足一定规则的内容。 $ grep ">" gene.fna | wc -l # 统计gene.fna文件中序列的条数$ grep -A 2 "3 gi 29732 34486" lastz.axt #将满足条件的行和下面两行显示出来 12grep -E # grep的拓展模式grep -P # 适应perl语言的正则表达式 区分一下:find是搜索目录下满足条件的文件,grep是搜索文件内满足条件的内容 4.2 三剑客之sedsedsed = Stream Editor流处理器,数据流过这个工具,格式化成固定的格式sed + 选项参数 + '模式' + 文本或文件 选项参数: 12345-e 替换,并输出到屏幕(搭配重定向)-i 原文件修改-f 根据模式替换-r 拓展的正则表达式-n 输出 模式: 1234g 全局s 替换,一个字符替换另一个d 删除p 打印 输出固定的行$ sed -n '1307p' seq.fna # 输出文件第1307行;$ sed -n '100,200p' seq.fna # 输出文件第100到200行; 替换操作$ sed -e 's/gi/GI/' seq.fna # 将文件中gi全部替换为大写GI;s为替换$ sed -i 's/gi/GI/g' seq.fna # 在原文件上进行替换,并且进行全部替换,g为全局(默认只进行一次替换) 删除操作$ sed -e '/^\\s$/d' seq.fna # 删除文件中的空白行,命令d为删除符合条件的行。\\s为空白;^行首,$行尾$ sed -e '/>/d' seq.fna # 删除包含ref的行,每个ref行都有>$ sed -e 's/:.*//g' seq.fna # 删除冒号之后的所有内容 4.3 三剑客之awkawk也是非常强大的文本处理工具,awk本身也是一门编程语言输出一个列表任意列$ awk '{print $1,$NF}' 1.txt # 输出1.txt的第一列和最后一列 过滤文件结果$ awk '{if ($3>=80 && $4>=100) print $0}' blast_m8.out # 过滤文件比对结果,将第三列值大于80,并且第四列值大于100的所有结果输出 比较$ awk '$8>$10' input.txt # 输出第8列数值大于第10列数值的行 输出固定行内容$ awk 'NR>=20&&NR<=80' input.txt #输出第20到第80行内容 5. 正则表达式正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑 123456789101112131415161718^ 匹配输入字行首 ^eat,识别eat开头的字符串$ 匹配输入行尾 eat$,识别eat结尾的字符串 \\b 单词锚定符 \\beat\\b ,只识别eat字符串. 匹配除“\\n”和"\\r"之外的任何单个字符\\ 转译字符 比如匹配. 则\\.* 匹配前面的子表达式任意次+ 匹配前面的子表达式一次或多次(大于等于1次,例如“zo+”能匹配“zo”以及“zoo”,但不能匹配“z”)# 需要grep -E支持(拓展)? 匹配前面的子表达式零次或一次 # 需要grep -E支持(拓展)[xyz] 字符集合。匹配所包含的任意一个字符x|y 匹配x或y。“z|food”能匹配“z”或“food”。“[z|f]ood”则匹配“zood”或“food”,择译匹配[a-z] 字符范围\\d 匹配所有数字,等同[0-9]\\s 空白,是字符集换页、制表、换行、回车以及空格的简写[\\f\\t\\n\\r]\\w [A-Za-z0-9_]单词包括大小写字母、数字和下划线^ 负值字符范围。匹配任何不在指定范围内的任意字符。(倒三角)\\D 非数字\\W 非字符\\S 非空白字符","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"linux指令","slug":"linux指令","permalink":"http://www.shelven.com/tags/linux%E6%8C%87%E4%BB%A4/"}]},{"title":"shell脚本基本语法总结","slug":"shell脚本基本语法总结","date":"2022-04-19T19:55:58.000Z","updated":"2022-12-03T16:17:05.000Z","comments":true,"path":"2022/04/20/a.html","link":"","permalink":"http://www.shelven.com/2022/04/20/a.html","excerpt":"简单记录下shell脚本语言的学习。","text":"简单记录下shell脚本语言的学习。 shell脚本运行方式首先要了解什么是脚本,脚本本质上是一个可运行的文件,使用特定格式的指令让系统通过脚本解析器解析并执行你的指令。系统提供的shell命令解析器有sh、bash和ash。可以通过echo $SHELL查看自己linux系统的默认解析方式 shell脚本文件的开头:#!/bin/bash #! 是特殊的用来声明脚本由什么shell解释,否则使用默认shell sh文件有三种执行方式./xxx.sh bash xxx.sh . xxx.sh ./xxx.sh 先按照 文件中#!指定的解析器解析,如果#!指定指定的解析器不存在才会使用系统默认的解析器 bash xxx.sh 指明先用bash解析器解析,如果bash不存在才会使用默认解析器 . xxx.sh 直接使用默认解析器解析 各种引号的区别vim创建脚本文件1111.sh: 123456789#!/bin/bashecho "Phantom的SHELL练习"num=123echo "预设数字=$num"read -p "输入数字" sum # read可以识别标准输入(键盘输入),-p参数设置提示语echo "输出结果=$sum+$num"echo "$sum" # ""解析变量值echo '$sum' # ''不能解析变量值echo "今天日期`date`" # ``识别为系统命令 变量名不能以数字开头 在变量赋值的过程中,等号两边不能接空格,若要接空格,则整个字符串都要用引号括起来 各种引号区别双引号“”可以解析变量的值单引号‘’不能解析变量的值,包含的变量会被当做字符串反引号`` 反引号的内容作为系统命令并执行 如`date` 各种括号的区别vim创建脚本文件xxx.sh: 1234567#!/bin/bashNum=1000{ # 花括号表示在当前shell完成,会影响当前变量 Num=1234 echo "()里面的数字是=$Num"}echo "显示当前shell数字=$Num" vim创建脚本文件xxxx.sh: 1234567#!/bin/bashNum=1000( # 小括号表示在当前shell完成,不会影响当前变量 Num=1234 echo "()里面的数字是=$Num")echo "显示当前shell数字=$Num" {命令序列} 在当前shell中执行,直接影响当前变量(命令序列) 由子shell完成,不影响当前shell的变量[判断条件]中括号是判断条件,进行数值判断。下面会说明 数值判断vim建立脚本文件xxxxx.sh: 12345678910#!/bin/bashread -p "请输入第一个数字" mread -p "请输入第二个数字" nif [ $m -eq $n ]; then # -eq 判断两个参数是否相等 echo "输入的两个数字相等"elif [ $m -lt $n ]; then # -lt 判断左边参数是否小于右边参数 echo "第一个数字小于第二个数字"elif [ $m -gt $n ]; then # -gt 判断左边参数是否大于右边参数 echo "第一个数字大于第二个数字"fi # if控制语句格式:if elif else fi 数值判断参数详解-eq 比较两个参数是否相等-ne 比较两个参数是否不相等-lt 左边参数是否小于右边参数-le 左边参数是否小于等于右边参数-gt 左边参数是否大于右边参数-ge 左边参数是否大于等于右边参数 字符串提取和替换vim新建脚本文件1234.sh: 1234567#!/bin/bashll="Phantom Aria f r u i t l e s s l o v e" # 定义字符串变量echo "长度为:${#ll}" # 字符串长度(包括空格)echo "${ll:3}" # 从第3个字符往后提取echo "${ll:3:11}" # 从第3个字符往后提取11个字符echo "${ll/ /}" # 字符串从左往右删除第一个空格(相当于替换的方式)echo "${ll// /}" # 删除字符串中所有空格(相当于全局替换的方式) 字符串匹配和删除vim新建脚本文件match.sh: 123456#!/bin/bashll="Phantom Aria fruitless love"echo ${ll% *} # 从右往左匹配第一个空格,删除右边所有字符串echo ${ll%% *} # 从右往左匹配所有空格,删除右边所有字符串echo ${ll#* } # 从左往右匹配第一个空格,删除左边所有字符串echo ${ll##* } # 从左往右匹配所有空格,删除左边所有字符串 *号是通配符,可以是匹配的任意长度任意字符串%和%%匹配原则:都是从右到左匹配,删除右边,%%称为贪婪匹配#和##匹配原则:都是从左往右匹配,删除左边,##同样称为贪婪匹配,注意通配符位置 for循环语句for循环语句两种写法如下: 123456789for ((初始值;限制值;执行步阶)) #注意两个小括号,少一个都不行do 程序段done或for 变量 in 1 2 3 4 5 6 7 8 9 10 #等价于`seq 1 10`do 程序段done vim建立脚本文件for_example.sh: 12345678#!/bin/bashdeclare -i sum=0 # 强制定义sum为整数型变量(不定义会变成一串字符串)read -p "请输入整数" n # 标准输入定义变量nfor (( i=0; i<=$n; i++ )) # 等同于for i in `seq 0 $n`,不赘述do sum=$sum+$i # 计算0到n之和doneecho "0到这个数的整数之和=$sum" 自定义函数vim建立脚本文件12345.sh: 12345678910111213#!/bin/bashfunction formax(){ if [ $n -gt $m ]; then return $n else return $m fi}read -p "输入数值1:" nread -p "输入数值2:" mformax $n $mecho "输入的最大值为$?" # $?表示上个指令的返回值 自定义了一个formax函数判断输入的两个数值大小,可以看出shell脚本中是一行一行读取指令的。自定义函数可以被引用,保存上述{}内的指令至原文件名12345.sh,在下一个脚本文件中,将函数放在脚本开始处, shell解释器发现它才可以进行调用(如下所示) vim建立脚本文件test.sh: 123456#!/bin/bashsource 12345.shread -p "输入数值1:" nread -p "输入数值2:" mformax $n $mecho "输入的最大值为$?" 自定义函数被成功调用","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"shell脚本","slug":"shell脚本","permalink":"http://www.shelven.com/tags/shell%E8%84%9A%E6%9C%AC/"}]},{"title":"转录组数据分析笔记(7)——DESeq2差异分析","slug":"转录组数据分析笔记(7)——R包DESeq2基因差异表达分析","date":"2022-04-18T07:49:20.000Z","updated":"2022-12-03T16:33:19.000Z","comments":true,"path":"2022/04/18/a.html","link":"","permalink":"http://www.shelven.com/2022/04/18/a.html","excerpt":"前面说到DESeq2包需要准备两个输入文件,一个是样本列表矩阵,一个是row count定量表达矩阵,接下来我们要对样本进行两两比对,找到两组之间有多少个基因上调和下调,不进行两两比对直接把4组数据4个重复全部导进去得到的结果是没有意义的,这里用DESeq2做表达基因的差异分析","text":"前面说到DESeq2包需要准备两个输入文件,一个是样本列表矩阵,一个是row count定量表达矩阵,接下来我们要对样本进行两两比对,找到两组之间有多少个基因上调和下调,不进行两两比对直接把4组数据4个重复全部导进去得到的结果是没有意义的,这里用DESeq2做表达基因的差异分析 举个栗子,我先进行短日照的“0”组样本和经过“1”天长日照的1组样本之间进行差异基因分析,从前面整理的样本列表矩阵我们可以看到,0组样本四个重复分别是ERR1698194、ERR1698202、ERR1698203和ERR1698204,同样1组数据4个重复分别为ERR1698205、ERR1698206、ERR1698207和ERR1698208,因此我们需要重新整理我们需要的数据做成csv格式,如下所示: 定量表达矩阵第一行需要和样本列表矩阵的第一列一一对应,顺序需要一模一样下面讲解如何使用DESeq2 1. 代码示范前面处理好raw count定量表达矩阵,建立样本列表矩阵后,我们就可以在rstudio里运行DESeq2包进行差异基因筛选了。代码如下。 1234567891011121314151617181920212223library("DESeq2")mycounts <- read.csv("gene_count_matrix_0_1.csv",row.names = 1)mycounts_1 <- mycounts[rowSums(mycounts) != 0,] # 重新定义数据集,过滤mapping数为0的基因mymeta <- read.csv("sample_list_0_1.csv",stringsAsFactors = T) # 载入样本分组文件,遇到字符串将其转化为因子colnames(mycounts_1) == mymeta$id # 检查导入的两个数据集是否匹配,返回值为F需要重新匹配mymeta$index <- factor(mymeta$index,levels = c("0","1")) # 把样本分组文件的分组列转换到因子,两两比对把对照组放前面!dds <- DESeqDataSetFromMatrix(countData = mycounts_1, colData = mymeta, design = ~index) #构造用于差异表达分析的数据集dds <- DESeq(dds)res <- results(dds)res_1 <- data.frame(res) # 结果res不是常规的数据,需要转化成数据框library("dplyr")res_1 %>% # dplyr给数据集增加新列 mutate(group = case_when( log2FoldChange >=1 & padj <=0.05 ~ "UP", log2FoldChange <=-1 & padj <=0.05 ~ "DOWN", TRUE ~ "NOT_CHANGE" )) -> res_2table(res_2$group)write.csv(res_2,file = "gene_0_1.csv", quote = F) # 输出文件 2. 代码详解详细解释一下过程: 在R里运行程序或者写代码,首先要确定好工作目录在哪里,将之前Stringtie转化的定量表达矩阵和样本列表矩阵全都放在工作目录下,这里我的表达量矩阵是transcript_count_matrix_0_1.csv,分组列表矩阵是sample_list_0_1.csv。getwd()可以查看当前工作目录,在全局设置里可以更改工作目录。 library("DESeq2") # 加载DESeq2这个R包 mycounts <- read.csv("gene_count_matrix_0_1.csv",row.names = 1) # 载入raw count矩阵,以第一列数据作为行名,读取的矩阵命名为mycounts mycounts_1 <- mycounts[rowSums(mycounts) != 0,] # 过滤每一行mapping总数为0的基因,将数据集整理命名为mycounts_1 mymeta <- read.csv("sample_list_0_1.csv",stringsAsFactors = T) # 载入样品列表,遇到字符串将其转化为一个因子 colnames(mycounts_1) == mymeta$id # 检查raw count矩阵第一行是否与样品列表的id列是否一致(如下)。这个很重要,不一致跑DESeq2会报错。如果显示false就要调整 mymeta$index <- factor(mymeta$index,levels = c("0","1")) # 这一步同样重要,把样本分组文件的分组列转换到因子,不然会报错。我这里对照组是第0天,所以把“1”放在“0”之后,这里顺序需要特别说明!样本和定量矩阵的分组只要一一对应可以不排序,这里一定要分清楚哪个组和哪个组进行比较,否则会得出完全相反的结论! 123dds <- DESeqDataSetFromMatrix(countData = mycounts_1, colData = mymeta, design = ~index) # 中间那一长串是DESeq2包里的函数,countData是raw count定量矩阵,colData是样品列表,design是分组信息,这步是为了构造用于差异表达分析的数据集,并将数据集命名为dds dds <- DESeq(dds) # 分析的核心DESeq程序 res <- results(dds) # 将结果输出至res数据集 res_1 <- data.frame(res) # res不是常规的数据,我们可以用head和class命令查看一下(如下图),需要转化成常规的数据框格式才可以对其进行加减列等操作,转换格式后的数据集名字为res_1 library("dplyr") # 加载这个包是为了对数据框进行操作,我是要增加新的一列统计差异表达情况 123456res_1 %>% mutate(group = case_when( log2FoldChange >=1 & padj <=0.05 ~ "UP", log2FoldChange <=-1 & padj <=0.05 ~ "DOWN", TRUE ~ "NOT_CHANGE" )) -> res_2 # 调用dplyr包给数据集增加新的一列group,log2FoldChange >=1,padj <=0.05,判断这个基因表达为上调,在log2FoldChange <=-1,padj <=0.05时判断这个基因表达为下调,其余情况为该基因表达情况不变。将结果输出到res_2数据集。 FoldChange表示两样品间表达量比值,是倍数变化,差异表达基因分析里,log2 fold change绝对值大于1为差异基因筛选标准。padj是调整后的p值,在p检验里,p值小于0.05是有显著差异的标志。 table(res_2$group) # 查看差异基因表达的结果,上调基因多少,下调基因有多少,不变的有多少 write.csv(res_2,file = "gene_0_1.csv", quote = F) # 输出和生成gene_0_1.csv文件,即为结果文件 3. 结果演示我对“0”组样本(对照)和长日照1天,2天和3天这一共4组样本分别进行两两对比,做了基因表达差异分析(0_1表示0组和1组对比,1_2表示1组和2组对比,依次类推不再赘述),同时与原文献补充数据2中的差异分析结果做对比 0组与1组对比,187个基因上调,149个基因下调;原文57个基因上调,79个基因下调 0组与2组对比,51个基因上调,42个基因下调;原文21个基因上调,24个基因下调 0组与3组对比,142个基因上调,143个基因下调;原文107个基因上调,84个基因下调 1组和2组对比,26个基因上调,29个基因下调;原文3个基因上调,0个基因下调 1组和3组对比,46个基因上调,50个基因下调;原文8个基因上调,2个基因下调 2组和3组对比,11个基因上调,13个基因下调;原文2个基因上调,1个基因下调 点击这里下载原文基因表达差异总表(补充数据2) 3.1 分析我做的差异分析总体趋势和文章类似,都是短日照组(SD组,也就是0组)与长日照1、2、3天组(LD组,分别对应123组)相比,有较多基因出现差异性表达;而长日照1、2、3天组之间相比差异表达基因较少,这样才合理,表明拟南芥茎尖分生组织在开花期长日照下,确实有不同的基因参与了光周期诱导的开花过程。如果要对这些差异表达的基因做更深入的分析(下游分析),我们就要比对GO库或者KEGG库进行代谢通路富集分析和注释。以后的笔记会说到。至于为什么我得到的差异基因普遍比原文多,一个是差异分析之前用的分析软件不同,在过滤数据过程中我的条件比较松(只过滤了count数为0的基因),另外不同软件的组装、比对和计数的算法实现也不一样。另外,我们得到这个基因差异表达结果之后,还可以做个更直观的火山图看我们的差异基因分布是否合理,之后也会说。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"转录组数据分析","slug":"转录组数据分析","permalink":"http://www.shelven.com/categories/%E8%BD%AC%E5%BD%95%E7%BB%84%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"}],"tags":[{"name":"DESeq2","slug":"DESeq2","permalink":"http://www.shelven.com/tags/DESeq2/"},{"name":"dplyr","slug":"dplyr","permalink":"http://www.shelven.com/tags/dplyr/"},{"name":"R语言","slug":"R语言","permalink":"http://www.shelven.com/tags/R%E8%AF%AD%E8%A8%80/"}]},{"title":"转录组数据分析笔记(6)——HTseq计数定量","slug":"转录组数据分析笔记(6)——HTseq计数定量","date":"2022-04-17T15:49:37.000Z","updated":"2022-12-03T16:33:39.000Z","comments":true,"path":"2022/04/17/b.html","link":"","permalink":"http://www.shelven.com/2022/04/17/b.html","excerpt":"HTseq也是对有参考基因组转录数据进行表达量分析的,主要用于reads计数。这个软件功能就比较专一,不像stringtie还需要运行prepDE.py脚本进行数据转化,直接一步到位。那为什么我一开始不用HTseq呢?因为我遇到一个bug 主要还是运算速度的问题,我比较了两种定量方式,HTseq定量虽然只有一步,但是速度远不如stringtie,也可能是我的问题,下面会说到。","text":"HTseq也是对有参考基因组转录数据进行表达量分析的,主要用于reads计数。这个软件功能就比较专一,不像stringtie还需要运行prepDE.py脚本进行数据转化,直接一步到位。那为什么我一开始不用HTseq呢?因为我遇到一个bug 主要还是运算速度的问题,我比较了两种定量方式,HTseq定量虽然只有一步,但是速度远不如stringtie,也可能是我的问题,下面会说到。 1. HTseq定量获得raw countvim一个新脚本,输入如下命令: 12345678#!/bin/bashfor i in `seq 194 209`do htseq-count -f bam -s no \\ /media/sf_/data/fastq/bam/ERR1698"$i".bam \\ # 输入bam文件 /media/sf_/data/ref/Arabidopis_thaliana.gtf # 参考基因组注释文件 > /media/sf_/data/fastq/count/ERR1698"$i".count # 自定义输出文件done 参数详解-f # 设置输入文件格式,可以是bam或者sam-s # 设置是否是链特异性测序,设置no每一条reads都会和正义链和反义链进行比较 保存运行以后发现这个程序只能分配一个线程(也可能是我没找到分线程的方法),所以可以根据电脑内核数分几个批处理一起运行会快很多(不然就等着干瞪眼= =)。 还有一点非常重要!bam文件需要提前按照名称排序,不然会出现绝大部分reads mapping不到参考基因组,这种情况会在屏幕上输出提示信息,但是程序还是会继续跑……这时候就别犹豫了赶紧kill这个程序,就算跑完了数据都不能用。可以用samtools sort -n对bam文件进行名称排序,但是排序之后无法再用samtools index建立索引文件,这会导致HTseq运行速度比蜗牛还慢。暂时没找到更好的办法 摊手。 经过漫长长长长长的时间等待,我们可以看看结果文件的head和tail(这里就放一张图吧): 前面记录了基因名称和mapping上的reads数,最后5行对应不同的mapping情况,在不同的模式下意义不同,官网给出的区别如下图,默认是union模式: 计数结果也可以用multiqc合并,生成在线报告,这里可以直观地看到每个样品比对上的reads数百分比,这里16个样品的比对率都超过80%,说明计数结果都还不错。 2. HTseq结果文件处理HTseq计数定量后得到的是每一个样品的每个基因reads数,我们需要合并每个样品定量数据,手动修改成DESeq2能识别的raw count表达矩阵,还需要再准备一个样本列表矩阵,才能进行后续的DESeq分析。参考一下stringtie最后生成的表达量矩阵文件,我们也需要将HTseq定量结果整理成csv格式(逗号作为分隔符),第一列是基因名,后面是按照样品序列的排序,中间是表达矩阵。 再来看一看HTseq定量生成的文件详情,同样第一列是基因名,后面是raw count数量,^I 表示两列数据是以制表符tab键分隔的,$为换行符。 我的方法比较笨比,除第一个ERR1698194.count文件保留外,其他所有count文件第一列删去并命名为cut.count,然后合并ERR1698194.count和其他所有cut.count文件,再将所有的制表符替换为逗号,最后加上第一行行名和改文件名。 用awk命令删除第一列,写入到新的cut.count文件中: 1234for i in `seq 195 209`do cat ERR1698"$i".count | awk '{$1 = ""; print $0}' > ERR1698"$i"cut.countdone paste组合ERR1698194样品和其他cut.count文件到alldata.count: $ paste ERR1698194.count *cut.count > alldata.count 看看alldata.count的数据格式,列数没有问题,但是awk删除列产生了空格: 用sed命令删除所有空格,替换所有制表符为逗号(两步可以合一步): $ sed 's/ //g' alldata.count > alldata1.count $ sed 's/\\t/,/g' alldata1.count > alldata2.count 这样就手动生成符合csv格式的文件了,只需加上第一行: 这里样本量比较少,我直接vim复制粘贴的方法加了第一行,重命名一下文件就完成了表达矩阵的制作,可以用于DESeq2分析了! 因为本人比较小白,上面处理过程就有些啰嗦了,总的思路就是改成csv格式文件的样式就可以。 样本列表矩阵的制作过程和stringtie一模一样,点击这里查看,本篇不再赘述。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"转录组数据分析","slug":"转录组数据分析","permalink":"http://www.shelven.com/categories/%E8%BD%AC%E5%BD%95%E7%BB%84%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"}],"tags":[{"name":"HTseq","slug":"HTseq","permalink":"http://www.shelven.com/tags/HTseq/"}]},{"title":"转录组数据分析笔记(5)——stringtie转录本组装和定量","slug":"转录组数据分析笔记(5)——stringtie转录本组装和定量","date":"2022-04-17T15:14:21.000Z","updated":"2022-12-03T16:35:23.000Z","comments":true,"path":"2022/04/17/a.html","link":"","permalink":"http://www.shelven.com/2022/04/17/a.html","excerpt":"本篇笔记主要记录如何用Stringtie做转录本的组装和定量,以及如何制作样本列表矩阵,为后面DESeq2分析做铺垫。","text":"本篇笔记主要记录如何用Stringtie做转录本的组装和定量,以及如何制作样本列表矩阵,为后面DESeq2分析做铺垫。 stringtie转录本组装和定量1 转录本组装Stringtie是一个基因和转录本组装定量的软件,stringtie的输入文件有两个,一个是经过排序的bam文件,排序可以用前面说到的samtools sort命令完成,还有一个是参考基因组的注释文件(gff或gtf格式)。 在使用Stringtie进行基因或者转录本组装定量的过程中,有一个非常重要的参数 - e,我之前跑了一遍流程没有加参数-e,结果组装的结果非常差,还有大量的未注释的基因。我请教了度娘,网上的教程攻略也都是抄来抄去的没解决什么问题,官网只有这么一句解释: -e this option directs StringTie to operate in expression estimation mode; this limits the processing of read alignments to estimating the coverage of the transcripts given with the -G option (hence this option requires -G). 对于加了参数-e之后如何做的比对和组装处理还是不明了,不知道表达评估模式的原理是什么,只能自己做个大概的总结(不知正确与否): 如果我们研究的样本没有很好的注释信息,研究的人少,现有的注释信息都不完善,那么我们就需要重建转录本进行注释,这个时候就不需要加参数-e。如果样品的注释信息非常完整,比如拟南芥这种模式生物,我们不需要重建新的转录本进行注释,只对现有的参考基因组注释文件就足够了,那就要用-e参数,不需要预测新的转录本。 -e参数还有个比较重要的地方,只有用了-e参数后,才可以运行prepDE.py3脚本得到read count矩阵(也就是进行定量),这个脚本后面会说。 我们首先创建一个shell脚本进行转录本组装: 12345678#!/bin/bashfor i in `seq 194 209`do stringtie -p 4 -e \\ -G /media/sf_/data/ref/Arabidopis_thaliana.gtf \\ # 参考基因组注释文件 -o /media/sf_/data/fastq/gtf/ERR1698"$i".gtf \\ # 自定义输出文件 /media/sf_/data/fastq/bam/ERR1698"$i".bam # 输入的bam文件done 保存,运行,我们可以得到.gtf格式文件,less一下查看里面的内容: 我们这里因为加了参数-e,不会有新的基因和转录本,可以看到每个read比对上的基因的信息。(不加参数-e会组装新基因和转录本,默认采用STRG加数字编号进行区分)。每行数据会给出coverage,FPKM和TPM三个信息,后两者都可以用来定量。FPKM和TPM都是对read counts数目进行的标准化,如果是单端测序数据可以用RPKM进行标准化,不进行数据标准化的比较是没有意义的。 2 合并转录本(重构转录本才需要)这一步要注意下,如果需要重构转录本才需要合并所有的转录本的组装结果,得到一个非冗余的转录本合集,也就是获得跨多个RNA-seq样品的全局的转录本。这里需要分两步: $ ls *.gtf > mergelist.txt # 将所有组装的转录本文件名合并到一个文件 $ stringtie --merge -p 4 -G /media/sf_/data/ref/Arabidopis_thaliana.gtf -o merge.gtf ./mergelist.txt #这一步是用--merge指令将所有转录本合并输出到merge.gtf文件中 我们最后得到的merge.gtf就是全局的转录本。这里只是记录一下这步操作,我们只关注参考基因组的注释结果就不需要merge。 3 获得定量表达矩阵DESeq2要求输入的定量结果为raw count形式,raw count是根据mapping到基因或转录本的reads数计算得到,而stringtie只提供了转录本水平的表达量,定量方式包括TPM和FPKM值两种。为了进行raw count定量,stringtie官方提供了prepDE.py脚本(两个版本,我选择的python 3版本,在我base环境下不会冲突),可以计算出raw count的表达量。 下载这个python脚本,如果你用的是windows浏览器,在官网找到脚本直接右键复制链接,用wget直接下到linux系统里,千万不要在windows上直接复制粘贴代码过去。因为windows的换行符和linux的不一样,两个系统间直接粘贴代码会出现错行和莫名其妙的缩进导致程序报错(可以用cat -A看两个系统换行符的区别,血的教训,排查了老半天才发现)推荐用prepDE.py3,不用再切python 2 的环境了。 官方给出的prepDE.py脚本有两种运行方式(如下图所示),一种是建立Ballgown能识别的目录结构,一种是建立sample_lst文件并指定所有样品数据的路径。两种方法都可行,Ballgown现在用的比较少,比较主流的还是Stringtie+DESeq2的分析方法。演示一下如何创建sample_lst和解释一下这个文件要求的格式。 3.1 sample_lst文件准备简单来说,sample_lst.txt要求第一列为样品编号,第二列为对应编号的样品gtf文件所在路径,中间用制表符tab隔开,如下图(命名不一定要完全一样,注意格式,后面要导入prepDE脚本,能找到就行): 这个文件准备工作比较简单,不再赘述 3.2 运行prepDE.py3将prepDE.py3脚本放在上面gtf文件的目录下,运行以下命令: $ python prepDE.py3 -i sample_lst.txt -g gene_count_matrix.csv -t transcript_count_matrix.csv 解释一下: 参数含义-i # 输入文件,就是前面做的sample_lst.txt-g # 自定义基因组表达矩阵名字,默认也是gene_count_matrix.csv-t # 自定义转录本表达矩阵名字,默认也是transcript_count_matrix.csv 得到的这两个文件就是基因和转录水平的raw count表达量矩阵,我们都可以用于后面的DESeq2分析。 4. 制作样本列表矩阵这里需要和前面为了运行prepDE.py脚本而制作的sample_lst文件区分开,要做下一步DESeq2差异基因分析,我们需要自己手动创建一个DESeq2能识别的样本列表矩阵,包含两列信息:一列是样本名称,一列是样本分组。样本分组信息我们可以直接从下载样本数据的地方(EBI官网)得到,只需要自己改一下格式。 下载之后发现第一行标题特别长,稍微处理下制表符替换成换行符,将第一行标题拆分成每个字段一行的格式,找一下不同天数处理的分组信息关键字“time”,发现我们要的分组信息在第36行(也就是原来文件的第36列): $ head -n1 E-MTAB-5130.sdrf.txt | tr '\\t' '\\n' | nl | grep "time" 同样的方法找样本信息所在列是32列: $ head -n1 E-MTAB-5130.sdrf.txt | tr '\\t' '\\n' | nl | grep "ENA" 所以我们需要提取第32列和第36列,用cut命令切割并重定向到新的文件sample_list: $ cut -f 32,36 E-MTAB-5130.sdrf.txt > sample_list.csv 发现相邻数据有重复,uniq删除重复行,再用sed替换制表符为逗号(因为csv文件就是以逗号作为分隔符),将原来的sample_list.csv覆盖,vim手动修改一下第一行名字,完成后就可以用于DESeq2分析了! $ uniq sample_list.csv > sample_list1.csv # uniq删除重复行 $ sed 's/\\t/,/g' sample_list1.csv > sample_list.csv # 替换制表符为逗号 手动修改sample_list .csv第一行内容,修改之后如下即可 更新2022/4/22:这里的处理仅仅是做到符合DESeq2输入的格式,在进行两组样本基因表达差异分析的时候,还需要分别建立两两比对的样本列表矩阵和定量矩阵,不然差异分析没有意义,详见DESeq2笔记","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"转录组数据分析","slug":"转录组数据分析","permalink":"http://www.shelven.com/categories/%E8%BD%AC%E5%BD%95%E7%BB%84%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"}],"tags":[{"name":"stringtie","slug":"stringtie","permalink":"http://www.shelven.com/tags/stringtie/"},{"name":"prepDE.py3","slug":"prepDE-py3","permalink":"http://www.shelven.com/tags/prepDE-py3/"}]},{"title":"转录组数据分析笔记(4)——IGV基因组浏览器安装和解读","slug":"转录组数据分析笔记(4)——IGV基因组浏览器安装和解读","date":"2022-04-16T11:32:50.000Z","updated":"2022-12-03T16:36:31.000Z","comments":true,"path":"2022/04/16/b.html","link":"","permalink":"http://www.shelven.com/2022/04/16/b.html","excerpt":"IGV(Integrative Genomics Viewer)是一个非常方便的比对软件,在使用前只需要将参考基因组和bam文件分别建立索引(即建立fai和bai文件)并载入,就可以对转录组测序数据进行可视化浏览。对比samtools tview功能,这个软件有交互式操作界面,对萌新非常友好。","text":"IGV(Integrative Genomics Viewer)是一个非常方便的比对软件,在使用前只需要将参考基因组和bam文件分别建立索引(即建立fai和bai文件)并载入,就可以对转录组测序数据进行可视化浏览。对比samtools tview功能,这个软件有交互式操作界面,对萌新非常友好。 1. IGV软件下载直接上百度搜就能找到IGV官网,选择linux版本或者windows版本都行,这里用linux版本为例,IGV只支持JAVA11版本,不用担心这个问题,下载的安装包里直接有JAVA11,解压就可以用,就是国外网站下载有点慢(科学上网)。 直接在虚拟机里解压打开,运行igv.sh,会自动准备好JAVA11的运行环境,成功弹出交互式界面(终于告别了黑漆漆的命令行 )。 2. 导入文件Genomes菜单栏上传建立索引的参考基因组.fa和.fai文件: File菜单栏上传排序并建立索引的.bam和.bai文件: 如果有参考基因组注释文件,同样可以导入进去,同样导入前需要sort排序和建立index,可以用菜单栏里的igvtools直接sort和index: 3. 界面解读我导入了5组bam数据,所有文件导入后可以看到如下界面,简单介绍一下各个区域和功能: 主页面获得的信息有限,我们选取第3条染色体为例,将其放大: 中间的界面可以通过左右拖动鼠标,或者按左右方向键来浏览染色体上的比对情况。我们在搜索框中直接搜基因名字进行染色体定位,比如CIPK家族中的CIPK7基因,回车后双击最后一栏基因注释文件中的基因名称CIPK7,可以得到详细的CIPK7基因信息(这里注意下,如果双击弹出来多个可供选择的片段的话,代表这个基因存在可变剪切): 在基因注释区右键,选择expanded,可以将CIPK7基因的所有转录本显示出来。 放大到一定程度后,我们可以看到基因注释区上方出现了核苷酸序列和氨基酸序列,我们可以点击sequence旁边的箭头,切换到另一条链的序列。 点击核苷酸,会出现三行,分别表示不同起始位点的核苷酸翻译结果,绿色为起始密码子,红色的星号表示终止密码子。 再来看看放大后的tracks区域,bam文件在载入后会默认生成两个tracks,一个显示测序深度(Coverage track,可以对比下samtools depth),一个显示比对情况(Alignment track),我们放大其中一个样本的数据信息。 Coverage track区域灰色代表质量好,如果reads中某核苷酸与参考序列超过20%不一致的时候,IGV会根据四个碱基的计数对coverage的条形图进行着色。这里可以看到该位点处有20个reads覆盖到,8条reads测的是C核苷酸,12条reads测的是T核苷酸。如果某个位置coverage条形图只有一种颜色,即该位点测的核苷酸和参考序列完全不一样,那说明该位点是SNP位点。 Alignment tracks柱形图是和bam文件中的数据一一对应的,举个例子,我在IGV软件的ERR1698206.bam可以看到在第3条染色体位置8173028有3条reads。虚拟机中找到这个bam文件,直接samtools view查看并grep这个位置,可以找到3条定位的reads(还有三条是配对的另一条链)。 如果一条reads中间有缺失,IGV会用黑色横杠表示,中间数字表示缺失几个核苷酸。 IGV还用不同颜色标记异常的插入片段大小的reads,这里做的是RNA-seq数据比对,不用看reads颜色,有些reads还在质控的时候被裁短了,变成蓝色很正常(因为比预期短,个人理解是这样,有待考证?)。以下是官网的默认着色方案:","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"转录组数据分析","slug":"转录组数据分析","permalink":"http://www.shelven.com/categories/%E8%BD%AC%E5%BD%95%E7%BB%84%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"}],"tags":[{"name":"IGV","slug":"IGV","permalink":"http://www.shelven.com/tags/IGV/"}]},{"title":"转录组数据分析笔记(3)——samtools用法小结","slug":"转录组数据分析笔记(3)——samtools用法小结","date":"2022-04-16T10:41:24.000Z","updated":"2022-12-03T16:37:26.000Z","comments":true,"path":"2022/04/16/a.html","link":"","permalink":"http://www.shelven.com/2022/04/16/a.html","excerpt":"本篇笔记主要记录samtools的用法。","text":"本篇笔记主要记录samtools的用法。 1. sam文件转化bambam文件是二进制文件,占用磁盘空间小,运算速度快,samtools操作是针对bam文件的,所以我们要进行数据转化。samtools sort指令可以将bam文件进行排序,这个指令同时也可以将sam文件转化成bam文件: 12345#!/bin/bashls *.sam | while read iddo samtools sort -l 0 -@ 5 -o $(basename $id ".sam").bam $id # 指定输出文件,改后缀.bamdone 运行脚本,将当前目录的sam文件全转换成bam文件并排序(这里的排序不是按名称,用HTseq还要再按照read名称排序,使用参数-n)。 samtools sort参数samtools sort # 对bam文件进行排序(sam文件排序不会变)-l # 设置输出文件压缩等级,0-9,0是不压缩,9是最高等级压缩-@ # 设置线程数-o # 设置排序后输出的文件名最后接输入的bam或者sam格式文件 2. 构建索引文件2.1 构建bam文件索引在bam文件目录下,排序后的bam文件可以建立索引: $ ls *.bam | xargs -i samtools index {} 注意下xargs -i的用法,和管道不一样,是传递参数给后一个命令的花括号中,后一个命令中不存在歧义的时候可省略参数-i和花括号。 如图生成的bai文件就是索引文件。其实到了这一步,前面的sam文件就可以删除(节省电脑空间),只留下bam文件就行,bam文件无法直接查看,可以通过samtools view命令查看bam文件。 2.2 构建参考基因组fa文件索引在参考基因组文件目录下,对参考基因组的fa文件建立索引: $ samtools faidx Arabidopsis_thaliana.dna.genome.fa 参考基因组文件名注意改成自己的,生成的索引文件是.fai结尾的 3. bam文件qc质控samtools转化生成的bam文件需要进行质控,看看比对情况如何。在bam文件目录下,我们创建一个samtools自带qc质控指令samtools flagstat运行脚本: 12345#!/bin/bashls *.bam | while read iddo samtools flagstat -@ 4 $id > $(basename $id ".bam").flagstat # 自定义输出文件done $ samtools flagstat bam文件 > 输出文件 # 这种格式,其他参数都一样 运行脚本文件可以获得16个.flagstat质控文件,和fastqc一样,我们还可以做完后用multiqc命令集合成一个html格式的总的qc报告网页。和fastqc不同之处是,fastqc是做下机数据质控,samtools是做比对参考基因组的质控。如下图所示,可以比较直观地看出大部分reads都是map上的。 生成的每一个flagstat文件我们也可以直接点开。 每一行统计数据都是以通过QC的reads数量和未通过QC的reads数量组成,以我点开的这个文件为例,主要信息有以下几个: 13992629个reads都是合格的12328290个reads只比对到参考基因组一个位置上13988737个reads比对到参考基因组(99.97%)12332182个reads是成对的12201338个reads可以正确配对(98.94%)2846条reads成对但只有一条能比对上参考基因组12398个配对的reads可以比对到别的染色体上 可以自己将所有的flagstat运行结束后的文件放在一个目录下,运用paste命令全部按列粘贴在一起,用cut或者awk提取所需的列数据自己做比对情况表格,这里不再赘述。 4. samtools其他指令简单介绍一下: $ samtools view ERR1698194.bam #查看bam文件(不能直接cat查看二进制文件) $ samtools tview ERR1698194.bam #类似于IGV这种基因组浏览器,但是非交互式界面(下图)不直观,我们一般都是用IGV查看基因组 其他还有samtools merge(合并所有bam文件到一个文件),samtools depth(得到每个碱基位点或者区域的测序深度,并输出到标准输出)等等,不是特别常用,这里就不介绍了。 在步骤2中构建的索引文件可以导入IGV中,对转录组每个read mapping情况进行可视化浏览,下个笔记将介绍IGV的用法。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"转录组数据分析","slug":"转录组数据分析","permalink":"http://www.shelven.com/categories/%E8%BD%AC%E5%BD%95%E7%BB%84%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"}],"tags":[{"name":"samtools","slug":"samtools","permalink":"http://www.shelven.com/tags/samtools/"}]},{"title":"转录组数据分析笔记(2)——使用Hisat2比对参考基因组","slug":"转录组数据分析笔记(2)——使用Hisat2比对参考基因组","date":"2022-04-15T08:53:41.000Z","updated":"2022-12-03T16:38:55.000Z","comments":true,"path":"2022/04/15/a.html","link":"","permalink":"http://www.shelven.com/2022/04/15/a.html","excerpt":"本片笔记主要记录Hisat2的用法,以及比较四个常用的比对参考基因组的软件。","text":"本片笔记主要记录Hisat2的用法,以及比较四个常用的比对参考基因组的软件。 1. 建立参考基因组索引在进行clean data与参考基因组比对之前,我们需要先建立参考基因组索引。进入下载好参考基因组的文件目录下,运行命令: $ hisat2-build Arabidopsis_thaliana.dna.genome.fa genome -p # 以几个线程运行,与电脑核数或者分配虚拟机的核数有关genome # 命名的索引文件名,可以改成自己能找到的 就可以在当前目录建立参考基因组索引文件,hisat2固定会生成8个以.ht2做后缀名的索引文件,如下所示: 需要注意的一点,比对软件除了hiasat2以外,还有subjunk、bwa、bowtie2等等,各个比对软件生成的索引文件是不同的,不能相互混用,命名的时候注意区分各种比对工具。 2. clean data与参考基因组比对比对的意思是将每一个read与参考基因组序列进行对比,目的是得到每一个read在参考基因组上的位置信息,有了这个基础的位置信息才可以进行后续基因或者转录本的定量,最终由定量结果做差异表达矩阵,分析上调或者下调的基因数量。 新建一个shell脚本输入下面的代码 12345678#!/bin/bashfor i in `seq 194 209`do hisat2 -p 4 -x /media/sf_example/data/ref/genome \\ #索引文件绝对路径 -1 /media/sf_example/data/clean_data/ERR1698"$i"_1.fq.gz \\ -2 /media/sf_example/data/clean_data/ERR1698"$i"_2.fq.gz \\ -S /media/sf_example/data/hisat2_sam/ERR1698"$i".sam #注意大写的Sdone 参数解释: -p # 同样是配置线程数-x # 指定索引文件,需要定义索引文件名称,不能加后缀,不能只定义到索引文件所在目录-1 # 第一端测序数据文件-2 # 第二端测序数据文件-S # 指定输出目录和文件,不指定会刷屏,注意是大写的S 输出到屏幕的结果如下,我们选取其中一个进行解读: 共有6166091对测序数据,都是双侧测序数据,其中: read1 和 read2 没有合理比对上参考基因组序列的有65259对,占1.06% read1 和 read2 只有一条比对上参考基因组序列的有5698903对,占92.42%,这部分reads数需要占测序reads的绝大多数才正常 read1 和 read2 可以同时比对到多个地方的有401929对,占6.52% 65259对没有合理比对上的序列中,55871对可以不合理地比对上一次 最后一块是对两条链拆开比对的结果,这个一般用不到,本来测序的两条reads就应该比对到同一个染色体同一个基因附近,拆开比对到不同染色体没有意义。我们要看的是最后一句话,总比对率为99.97%,通常比对率大于90%说明比对情况较好,与参考基因组基本吻合。 3. sam文件解读比对结果除了有屏幕上输出的总体报告外,还有记录详细比对结果的sam文件。双端测序的比对会将两个测序文件进行整合和比较,最后只生成一个sam文件,因此这个sam文件非常大,hisat2比对生成的sam文件可以直接打开。我们可以选取一部分进行解读。 @HD VN:1.0 SO:unsorted (排序类型) VN是格式版本;SO表示比对排序的类型,有unknown,unsorted,queryname和coordinate几种。samtools软件在进行行排序后不能自动更新sam文件的SO值。 @SQ SN:1 LN:30427671 (序列ID及长度) 参考序列名,这些参考序列决定了比对结果sort的顺序,SN是参考序列名;LN是参考序列长度;每个参考序列为一行。这里表示拟南芥有5条染色体,对应长度都在后面,Mt是线粒体基因,Pt是叶绿体基因。 @PG ID:hisat2 PN:hisat2 VN:2.2.1 (比对所使用的软件及版本) 这里包括了路径,方法,以及我质控后的序列长度(50-100)等详细信息。 接下来每行都是一长串,显示的是比对结果部分,11个字段(列) 第一列:QNAME:测序出来的reads序列数据名,ERR1698194.2 第二列:FLAG:表明比对类型:paring,strand,mate strand等 第三列:RNAME:参考基因组的染色体名,我这里是第1条染色体 第四列:POS:比对到这个染色的具体位置,4969 第五列:MAPQ:比对质量,是一个衡量比对好坏的打分结果,60最好 第六列:CIGAR:简要比对信息表达式,1S100M是第1个碱基切除,100个匹配 第七列:RNEXT:另一个序列比对上的参考序列编号,没有另外的片段是*,同一个片段= 第八列:MPOS:另一个序列匹配的染色体具体位置,这里一样也是4969 第九列:TLEN:配对片段长度,最左边的为正,最右边的为负 第十列:SEQ:和参考序列在同一个链上比对的序列 第十一列:QUAL:比对序列的质量和reads碱基质量值 后面提供额外的信息,一般不重要,了解一下就行。因为sam文件太大(往往有10G以上),也不适合电脑进行后续处理,所以我们会用到samtools,将sam文件转化为更适合电脑处理的二进制bam文件。这个后面会讲。 4. 其他比对软件以下4种软件均用于序列比对,用法稍有不同,做个记录 $ hisat2 -p 4 -x 索引目录 -1 单端测序数据文件 -2 另一端测序数据文件 -S 输出文件 $ subjunk -T 4 -i 索引目录 -r 单端测序数据文件 -R 另一端测序数据文件 -o 输出文件 $ bowtie2 -p 4 -x 索引目录 -1 单端测序数据文件 -2 另一端测序数据文件 -S 输出文件 $ bwa mem -t 4 -M 索引目录 单端测序数据文件 另一端测序数据文件 > 输出文件","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"转录组数据分析","slug":"转录组数据分析","permalink":"http://www.shelven.com/categories/%E8%BD%AC%E5%BD%95%E7%BB%84%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"}],"tags":[{"name":"hisat2","slug":"hisat2","permalink":"http://www.shelven.com/tags/hisat2/"}]},{"title":"转录组数据分析笔记(1)——如何用fastqc和trim-galore做测序数据质控","slug":"转录组数据分析笔记(1)——如何用fastqc和trim-galore做测序数据质控","date":"2022-04-14T13:13:35.000Z","updated":"2022-12-03T16:39:16.000Z","comments":true,"path":"2022/04/14/a.html","link":"","permalink":"http://www.shelven.com/2022/04/14/a.html","excerpt":"本系列学习笔记数据均来自”Temporal dynamics of gene expression and histone marks at the Arabidopsis shoot meristem during flowering“,原文用RNA-Seq的方式研究开花阶段,芽分生组织不同时期的基因表达量变化,4个时间段(0, 1, 2, 3),4个重复,共有16个样品。点击这里获取文献","text":"本系列学习笔记数据均来自”Temporal dynamics of gene expression and histone marks at the Arabidopsis shoot meristem during flowering“,原文用RNA-Seq的方式研究开花阶段,芽分生组织不同时期的基因表达量变化,4个时间段(0, 1, 2, 3),4个重复,共有16个样品。点击这里获取文献 1. 读文章获得RNA-Seq数据从文章末尾我们可以获得一些测序数据信息: Data availability. ChIP-seq and RNA-seq data have been deposited with ArrayExpress database (www.ebi.ac.uk/arrayexpress), accession numbers E-MTAB-4680, E-MTAB-4684 and E-MTAB-5130. 可以看到作者将CHIP-seq和RNA-seq数据上传到ArrayExpress这个数据库中,这个数据库是欧洲生物信息研究所(European Bioinformatics Institute, EBI)旗下的公共数据库,主要用于存放芯片和高通量测序数据,我们可以直接从该数据库中下载我们需要的RNA-seq数据,自己动手分析。 顺便介绍一下,欧洲EBI旗下的ENA数据库,美国NCBI旗下的GenBank,以及日本的DDBJ三大巨头组成了国际核酸序列数据库合作联盟(INSDC),这三大数据库收录了世界上报道的所有序列数据。 EBI数据库可以直接下载fastq数据,不需要做SRA数据转换(NCBI数据库中下载sra数据则需要转换,需要用工具fastq-dump),这是EBI数据库下载高通量测序数据的优点,但是这个数据库经常网络连接不稳定,用aspera或者prefetch这种高速下载软件也不一定能稳定下载 最好的方法是科学上网。我们可以从ArrayExpress数据库中输入索引号E-MTAB-5130,直接获得样本信息和测序信息。 2. 测序数据质控我们可以看到,下载的数据是双端测序产生的。我们不能直接用下载的raw data做后续分析,必须要进行质控查看测序质量如何。 2.1 使用fastqc对测序数据生成质控报告下载好的fastq文件可以直接用fastqc工具做测序数据质控,输入以下命令一次生成所有qc报告: $ fastqc *.fastq.gz -o ./ #在当前目录下对所有.fastq.gz文件生成qc报告,-o参数定义输出目录 运行结束后我们可以得到.html文件和.zip压缩包,这个就是质控报告。在虚拟机里,我们可以直接点开.html后缀的网页文件查看质控报告(和压缩包的内容是一致的)。 顺便介绍一个非常好用的工具multiqc,可以通过conda install直接安装,这个工具可以将批量生成的qc报告合并为一个,看起来更加直观。在生成qc报告的当前目录下,运行代码: $ multiqc ./ 2.2 质控报告解读2.2.1 基本信息绿色表示通过,黄色表示不太好,红色表示不通过。RNA-seq一般在Sequence Duplication Levels上结果会不好,一个基因可能会大量表达,测到好多遍。 2.2.2 核苷酸测序质量箱式图这里测序质量(纵坐标)用Q值表示,p为出错率,Q值计算式为Q=-10*lg(p)。每一个核苷酸的测序质量可以从fastq文件第四行一一对应上,这里只是做了统计和可视化。我们可以看到每个位点的核苷酸测序质量Q值都在30以上,意味着每个位点的测序正确率都在99.9%以上,可以认为测序质量比较好。 箱式图解读:黄色箱子(25%和75%的分数线),红色线(中位数),蓝线是平均数,下面和上面的触须分别表示10%和90%的点。 2.2.3 测序泳道质量图纵坐标为tile编号,这张图代表每次荧光扫描的质量。蓝色背景表明测序质量良好,白色和红色的背景表示测序过程中可能有小气泡或者测序泳道上有污染。直接的体现就是部分测序数据中出现连续的N,也就是不能读取,可能是任何一个核苷酸。 2.2.4 reads质量得分可以看到平均质量在38,质量比较高。如果最高峰所对应的横坐标质量值小于27(错误率0.2%) 则会显示“警告”,如果最高峰的质量值小于20(错误率1%)则会显示“不合格”。 2.2.5 每条reads各个测序位点上各碱基出现概率图上看得出比较稳定,测序刚开始的时候波动会大一点,这里的GC含量和AT含量不一致。如果任何一个位置上的A和T之间或者G和C之间的比例相差10%以上则报“警告”,任何一个位置上的A和T之间或者G和C之间的比例相差20%以上则报“不合格”。 2.2.6 GC含量和理论分布可以看出GC含量在43%左右,与理论分布(也就是正态分布)比较吻合,中心峰值与所测转录组的GC含量一致。如果有不正常的尖峰,可能是测序文库有污染,接头的污染还会在过表达序列中体现。 2.2.7 每条reads的含N碱基数不能识别的碱基会被读成N,这里没有N,测序质量非常好。横坐标表示reads的位置,纵坐标表示N的比例。如果任何一个位置N的比例大于5%则报“警告”,大于20%则报“失败”。 2.2.8 测序长度分布这个测序仪一次测量长度是101bp。测序仪出来的原始reads通常是均一长度的,经过质控软件处理过的reads长度则不一样,这里说明测序结果较好。 2.2.9 重复序列水平可以看到重复水平较低。图中横轴代表reads的重复次数,大于10次重复后则按不同的重复次数合并显示。纵坐标表示各重复次数下的reads数占总reads的百分比;蓝线展示所有reads的重复情况,红线表示在去掉重复以后,原重复水平下的reads占去重后reads总数的百分比;如果非unique的reads占总reads数的20%以上则报 ”警告“,占总read数的50%以上则报 ”不合格“。这项变黄是正常的。 2.2.10 过表达序列和接头序列过表达的序列很可能是一些测序的接头序列,这里两种序列都看不到,说明质量良好。过表达序列是显示同一条reads出现次数超过总测序reads数的0.1%的统计情况,超过0.1%则报“警告”,超过1%则报“不合格”,会列出可能的接头序列。接头序列正常情况下含量接近于0。 2.3 trim-galore测序数据质控过滤质控的目的使为了除去下机数据raw data中的接头序列和质量比较差的测序数据,Q<20,正确率小于99%,如果这样的核苷酸超过read长度的20%,则考虑将该read丢弃(只是建议,不是强制,根据需要可以自定义过滤条件)。 trim-galore也可以用conda install安装,非常方便,这是一个自动检测adaptor的软件,可以一个命令自动找出主流的测序接头并去除,还可以设置参数对测序数据质控。简单介绍一下trim-galore的一些参数: -q # 设定Phred quality score阈值,默认为20;-phred33 # 测序平台衡量测序质量的方法,有33和64,不影响;-length # 设定输出reads长度阈值,小于设定值会被抛弃,根据需要设计;-stringency # 设定可以忍受的前后adapter重叠的碱基数,默认为1(非常苛刻);-paired # 用于分析双端测序数据结果;-o # 输出目录 因为是双端测序,16个样本每个都有_1和_2两个文件,可以写个脚本批量运行: 12345678#!/bin/bashfor i in `seq 194 209` do trim_galore -q 25 -phred33 -length 50 -stringency 3 -paired \\ -o /media/sf_/example/data/clean_data \\ /media/sf_/example/data/raw_data/ERR1698"$i"_1.fastq.gz \\ #一端测序数据 /media/sf_/example/data/raw_data/ERR1698"$i"_2.fastq.gz #另一端测序数据done 保存退出,运行,最后生成的_triming_report.txt文件就是生成的质控报告,_val_1.fq.gz就是过滤后瘦身的clean data,我们可以看到大小比原来小了10M左右,这个clean data才可以用于后续的分析流程 我截取了其中一个数据的质控结果,拉到最底下,可以看到两端测序数据中都有AGATCGGAAGAGC这个序列,在一个样本测序数据中出现240027次经过网上查找,AGATCGGAAGAGC这个序列确实是Illumina公司测序时的接头序列(点击这里查看),可以和上面fastqc质控报告中的测序平台Illumina相互验证。","categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"转录组数据分析","slug":"转录组数据分析","permalink":"http://www.shelven.com/categories/%E8%BD%AC%E5%BD%95%E7%BB%84%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"}],"tags":[{"name":"fastqc","slug":"fastqc","permalink":"http://www.shelven.com/tags/fastqc/"},{"name":"multiqc","slug":"multiqc","permalink":"http://www.shelven.com/tags/multiqc/"},{"name":"trim-galore","slug":"trim-galore","permalink":"http://www.shelven.com/tags/trim-galore/"}]},{"title":"小破站正式对外开放啦!","slug":"小破站正式对外开放啦!","date":"2022-04-13T14:29:14.000Z","updated":"2023-07-04T10:13:40.000Z","comments":true,"path":"2022/04/13/a.html","link":"","permalink":"http://www.shelven.com/2022/04/13/a.html","excerpt":"咳咳,经过10天左右紧张地准备,小站今天正式对外开放啦!作为第一次运行个人网站的小白,看着网站从零开始在自己手上慢慢展现一个个页面,实现一个个功能,这种成就感和激动感,让我感觉这几天的熬夜狂肝还是值得的呜呜呜我的头发。","text":"咳咳,经过10天左右紧张地准备,小站今天正式对外开放啦!作为第一次运行个人网站的小白,看着网站从零开始在自己手上慢慢展现一个个页面,实现一个个功能,这种成就感和激动感,让我感觉这几天的熬夜狂肝还是值得的呜呜呜我的头发。 建站过程为什么建站说是从零开始,其实也是站在前人搭建好的框架上才能顺利进行的。我很早之前就萌发了搭建个人网站的想法,自从这个学期开始学习生物信息学,我也慢慢对linux系统有了更深入的理解。一开始只是在虚拟机上跑跑程序,后来就想着不如买一个云服务器装linux玩玩,既然买了服务器了,那就再绑个域名吧,既然两个都有了,不如就再建个网站吧(滑稽)。于是趁着腾讯云的轻量级应用服务器打折的机会,一次性买了3年…然后又在阿里云买了个域名,了解到需要备案后才能解析域名,行,又办理各种手续在工信部备了案。不得不说,在各大云服务器商内卷搞活动的时候,有个学生认证还是相当香的。至于怎么用服务器,那就是后面考虑的事了。 备案和备案期间的学习我在腾讯云买的服务器,通过接入商腾讯云协助,腾讯云先审核我的材料,通过以后再提交工信部备案,备案还是相当快的,3天时间就办下来了。备案期间也没闲着,作为一个前端小白的我,又去恶补了一些前端知识,比如什么是css、js、ejs、html文件,这些文件的格式是怎么样的,java的一些基本语法等等。学习的折磨程度不亚于刚开始学R语言和linux操作系统,不过有了一些shell脚本的语法知识以后,还是能感觉到这些语言之间还是有共同的判断方式和逻辑在里面的(纯小白发言,不知道对不对)。在慢慢摸索的过程中痛并快乐着,先是照着别人给的js文件魔改,再是自己调试遇到的问题和bug,尤其在发现bug最后解决bug的时候,那种成就感能给我带来莫大的快乐。 建站历程建站的过程是痛苦的,踩了非常多的坑,我觉得我甚至可以写好几篇攻略出来。我一开始的想法是在github建库搭建个人网站,从安装nodejs和npm这种最基础的开始,配置环境,用hexo框架搭建一个本地静态博客,然后部署到github空间,这样就可以用github仓库名访问我的网站。但是有一个非常大的问题,github从国内访问会有DNS污染,连接速度那叫一个绝望。我自己是可以科学上网,但是总不能让别人浏览我网页的时候也科学上网吧?我也不太相信有很多人会用改host的办法来访问github,于是我就萌生了将买的云服务器用来搭建网站的想法(我知道这是一种资源浪费),github就可以当做网站的备份,以后即使我的云服务器过期了,我也可以依旧正常访问搭建在github里的静态博客。所以我的部署过程有点绕,就是本地生成静态博客,先部署到github仓库,再同步部署到我的云服务器。这样我就可以用备案后的二级域名解析到云服务器,在通过安装httpd服务来开启外部的访问了。 可以访问我的网站还是第一步,还要做好安全防护,申请SSL安全证书才能开启https连接。免费申请方式也很多,我申请了一年的apache上的SSL安全证书,然后安装到自己服务器上。还想吐槽一下,腾讯云有一键部署SSL安全证书通道,要90块钱,只要有点linux文本操作基础,自己按照教程部署一下半小时左右就能完成,这钱真好赚。SSL证书安装做好以后,就可以上别的云服务商找找免费的CDN加速了,有CDN加速一是可以加快网站的加载速度,二是隐藏自己服务器的ip地址,能起到一定的网站安全防护作用。吹一波又拍云,只要在网站底下加上他们的标志,启用他们的CDN加速,就能申请加入又拍云联盟,有免费一年的CDN加速和云储存服务,还可以查看访问记录等等 学生党薅羊毛的利器23333 。因为我的网页图片比较多,所以就应用了网页图片加速。 后记具体过程比如怎么接入第三方各种网站,用什么主题,怎么美化页面等等,就不详细说了,说多了肝疼,以后有想法再更新如何从零开始搭建自己的博客吧!至少没有服务器和域名也是完全可以实现的。建立这个小破站也主要是为了上传自己的学习笔记,整理生信网站和工具合集(相应的栏目还在建设中 新建文件夹了 ),督促自己学习hhhhh 本人技术实力有限,也不想搞地太花里胡哨,之后可能会有一些简单的小功能接入,还有移动端浏览小破站的优化(现在移动端浏览这个小破站简直是灾难,我都看不下去了),太费心思的东西就暂时放放了,主要专注于内容的创作,这几天会把一些学习笔记陆续上传。本人也是第一次用markdown语法写东西,排版一直搞不定段首的两个空格,先这样吧。 开摆","categories":[{"name":"个人主页","slug":"个人主页","permalink":"http://www.shelven.com/categories/%E4%B8%AA%E4%BA%BA%E4%B8%BB%E9%A1%B5/"}],"tags":[{"name":"建站","slug":"建站","permalink":"http://www.shelven.com/tags/%E5%BB%BA%E7%AB%99/"}]}],"categories":[{"name":"学习笔记","slug":"学习笔记","permalink":"http://www.shelven.com/categories/%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"},{"name":"表观遗传学","slug":"表观遗传学","permalink":"http://www.shelven.com/categories/%E8%A1%A8%E8%A7%82%E9%81%97%E4%BC%A0%E5%AD%A6/"},{"name":"QQ机器人","slug":"QQ机器人","permalink":"http://www.shelven.com/categories/QQ%E6%9C%BA%E5%99%A8%E4%BA%BA/"},{"name":"基因组三代测序分析","slug":"基因组三代测序分析","permalink":"http://www.shelven.com/categories/%E5%9F%BA%E5%9B%A0%E7%BB%84%E4%B8%89%E4%BB%A3%E6%B5%8B%E5%BA%8F%E5%88%86%E6%9E%90/"},{"name":"编程自学","slug":"编程自学","permalink":"http://www.shelven.com/categories/%E7%BC%96%E7%A8%8B%E8%87%AA%E5%AD%A6/"},{"name":"群体基因组学","slug":"群体基因组学","permalink":"http://www.shelven.com/categories/%E7%BE%A4%E4%BD%93%E5%9F%BA%E5%9B%A0%E7%BB%84%E5%AD%A6/"},{"name":"三维基因组学","slug":"三维基因组学","permalink":"http://www.shelven.com/categories/%E4%B8%89%E7%BB%B4%E5%9F%BA%E5%9B%A0%E7%BB%84%E5%AD%A6/"},{"name":"单细胞转录组","slug":"单细胞转录组","permalink":"http://www.shelven.com/categories/%E5%8D%95%E7%BB%86%E8%83%9E%E8%BD%AC%E5%BD%95%E7%BB%84/"},{"name":"比较基因组学","slug":"比较基因组学","permalink":"http://www.shelven.com/categories/%E6%AF%94%E8%BE%83%E5%9F%BA%E5%9B%A0%E7%BB%84%E5%AD%A6/"},{"name":"个人主页","slug":"个人主页","permalink":"http://www.shelven.com/categories/%E4%B8%AA%E4%BA%BA%E4%B8%BB%E9%A1%B5/"},{"name":"网络相关","slug":"网络相关","permalink":"http://www.shelven.com/categories/%E7%BD%91%E7%BB%9C%E7%9B%B8%E5%85%B3/"},{"name":"深度学习","slug":"深度学习","permalink":"http://www.shelven.com/categories/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/"},{"name":"转录组数据分析","slug":"转录组数据分析","permalink":"http://www.shelven.com/categories/%E8%BD%AC%E5%BD%95%E7%BB%84%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90/"}],"tags":[{"name":"ATAC-seq","slug":"ATAC-seq","permalink":"http://www.shelven.com/tags/ATAC-seq/"},{"name":"CUT&Tag","slug":"CUT-Tag","permalink":"http://www.shelven.com/tags/CUT-Tag/"},{"name":"qq bot","slug":"qq-bot","permalink":"http://www.shelven.com/tags/qq-bot/"},{"name":"go-cqhttp","slug":"go-cqhttp","permalink":"http://www.shelven.com/tags/go-cqhttp/"},{"name":"Hi-C染色体挂载","slug":"Hi-C染色体挂载","permalink":"http://www.shelven.com/tags/Hi-C%E6%9F%93%E8%89%B2%E4%BD%93%E6%8C%82%E8%BD%BD/"},{"name":"3D-DNA","slug":"3D-DNA","permalink":"http://www.shelven.com/tags/3D-DNA/"},{"name":"JBAT","slug":"JBAT","permalink":"http://www.shelven.com/tags/JBAT/"},{"name":"juicer2","slug":"juicer2","permalink":"http://www.shelven.com/tags/juicer2/"},{"name":"python","slug":"python","permalink":"http://www.shelven.com/tags/python/"},{"name":"群体基因组学","slug":"群体基因组学","permalink":"http://www.shelven.com/tags/%E7%BE%A4%E4%BD%93%E5%9F%BA%E5%9B%A0%E7%BB%84%E5%AD%A6/"},{"name":"Tassel5","slug":"Tassel5","permalink":"http://www.shelven.com/tags/Tassel5/"},{"name":"Plink","slug":"Plink","permalink":"http://www.shelven.com/tags/Plink/"},{"name":"HiC-Pro","slug":"HiC-Pro","permalink":"http://www.shelven.com/tags/HiC-Pro/"},{"name":"HiCPlotter","slug":"HiCPlotter","permalink":"http://www.shelven.com/tags/HiCPlotter/"},{"name":"HiTC","slug":"HiTC","permalink":"http://www.shelven.com/tags/HiTC/"},{"name":"Seurat","slug":"Seurat","permalink":"http://www.shelven.com/tags/Seurat/"},{"name":"MCScanX","slug":"MCScanX","permalink":"http://www.shelven.com/tags/MCScanX/"},{"name":"共线性分析","slug":"共线性分析","permalink":"http://www.shelven.com/tags/%E5%85%B1%E7%BA%BF%E6%80%A7%E5%88%86%E6%9E%90/"},{"name":"TEsorter","slug":"TEsorter","permalink":"http://www.shelven.com/tags/TEsorter/"},{"name":"Barker3","slug":"Barker3","permalink":"http://www.shelven.com/tags/Barker3/"},{"name":"容器","slug":"容器","permalink":"http://www.shelven.com/tags/%E5%AE%B9%E5%99%A8/"},{"name":"singularity","slug":"singularity","permalink":"http://www.shelven.com/tags/singularity/"},{"name":"tRNAscan-SE","slug":"tRNAscan-SE","permalink":"http://www.shelven.com/tags/tRNAscan-SE/"},{"name":"Rfam/Infernal","slug":"Rfam-Infernal","permalink":"http://www.shelven.com/tags/Rfam-Infernal/"},{"name":"建站","slug":"建站","permalink":"http://www.shelven.com/tags/%E5%BB%BA%E7%AB%99/"},{"name":"RepeatModeler","slug":"RepeatModeler","permalink":"http://www.shelven.com/tags/RepeatModeler/"},{"name":"RepeatMasker","slug":"RepeatMasker","permalink":"http://www.shelven.com/tags/RepeatMasker/"},{"name":"GMATA","slug":"GMATA","permalink":"http://www.shelven.com/tags/GMATA/"},{"name":"TRF","slug":"TRF","permalink":"http://www.shelven.com/tags/TRF/"},{"name":"ChatGPT","slug":"ChatGPT","permalink":"http://www.shelven.com/tags/ChatGPT/"},{"name":"GATK","slug":"GATK","permalink":"http://www.shelven.com/tags/GATK/"},{"name":"QUAST","slug":"QUAST","permalink":"http://www.shelven.com/tags/QUAST/"},{"name":"BUSCO","slug":"BUSCO","permalink":"http://www.shelven.com/tags/BUSCO/"},{"name":"LAI","slug":"LAI","permalink":"http://www.shelven.com/tags/LAI/"},{"name":"NextPolish","slug":"NextPolish","permalink":"http://www.shelven.com/tags/NextPolish/"},{"name":"Racon","slug":"Racon","permalink":"http://www.shelven.com/tags/Racon/"},{"name":"NextDenovo","slug":"NextDenovo","permalink":"http://www.shelven.com/tags/NextDenovo/"},{"name":"Canu","slug":"Canu","permalink":"http://www.shelven.com/tags/Canu/"},{"name":"github","slug":"github","permalink":"http://www.shelven.com/tags/github/"},{"name":"内网穿透","slug":"内网穿透","permalink":"http://www.shelven.com/tags/%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F/"},{"name":"代理服务器","slug":"代理服务器","permalink":"http://www.shelven.com/tags/%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8/"},{"name":"爬虫","slug":"爬虫","permalink":"http://www.shelven.com/tags/%E7%88%AC%E8%99%AB/"},{"name":"requests","slug":"requests","permalink":"http://www.shelven.com/tags/requests/"},{"name":"Xpath","slug":"Xpath","permalink":"http://www.shelven.com/tags/Xpath/"},{"name":"selenium","slug":"selenium","permalink":"http://www.shelven.com/tags/selenium/"},{"name":"HTTP","slug":"HTTP","permalink":"http://www.shelven.com/tags/HTTP/"},{"name":"人工智能","slug":"人工智能","permalink":"http://www.shelven.com/tags/%E4%BA%BA%E5%B7%A5%E6%99%BA%E8%83%BD/"},{"name":"google colab","slug":"google-colab","permalink":"http://www.shelven.com/tags/google-colab/"},{"name":"Tacotron2","slug":"Tacotron2","permalink":"http://www.shelven.com/tags/Tacotron2/"},{"name":"HiFiGAN","slug":"HiFiGAN","permalink":"http://www.shelven.com/tags/HiFiGAN/"},{"name":"拆包","slug":"拆包","permalink":"http://www.shelven.com/tags/%E6%8B%86%E5%8C%85/"},{"name":"声纹识别","slug":"声纹识别","permalink":"http://www.shelven.com/tags/%E5%A3%B0%E7%BA%B9%E8%AF%86%E5%88%AB/"},{"name":"语音转文本","slug":"语音转文本","permalink":"http://www.shelven.com/tags/%E8%AF%AD%E9%9F%B3%E8%BD%AC%E6%96%87%E6%9C%AC/"},{"name":"反向代理","slug":"反向代理","permalink":"http://www.shelven.com/tags/%E5%8F%8D%E5%90%91%E4%BB%A3%E7%90%86/"},{"name":"blast","slug":"blast","permalink":"http://www.shelven.com/tags/blast/"},{"name":"kmergenie","slug":"kmergenie","permalink":"http://www.shelven.com/tags/kmergenie/"},{"name":"SOAPdenovo2","slug":"SOAPdenovo2","permalink":"http://www.shelven.com/tags/SOAPdenovo2/"},{"name":"jellyfish","slug":"jellyfish","permalink":"http://www.shelven.com/tags/jellyfish/"},{"name":"GenomeScope2.0","slug":"GenomeScope2-0","permalink":"http://www.shelven.com/tags/GenomeScope2-0/"},{"name":"blast+","slug":"blast","permalink":"http://www.shelven.com/tags/blast/"},{"name":"nanoplot","slug":"nanoplot","permalink":"http://www.shelven.com/tags/nanoplot/"},{"name":"filtlong","slug":"filtlong","permalink":"http://www.shelven.com/tags/filtlong/"},{"name":"BLAST+","slug":"BLAST","permalink":"http://www.shelven.com/tags/BLAST/"},{"name":"aspera","slug":"aspera","permalink":"http://www.shelven.com/tags/aspera/"},{"name":"perl","slug":"perl","permalink":"http://www.shelven.com/tags/perl/"},{"name":"bioperl","slug":"bioperl","permalink":"http://www.shelven.com/tags/bioperl/"},{"name":"集群","slug":"集群","permalink":"http://www.shelven.com/tags/%E9%9B%86%E7%BE%A4/"},{"name":"slurm","slug":"slurm","permalink":"http://www.shelven.com/tags/slurm/"},{"name":"AnnotationHub","slug":"AnnotationHub","permalink":"http://www.shelven.com/tags/AnnotationHub/"},{"name":"GO/KEGG","slug":"GO-KEGG","permalink":"http://www.shelven.com/tags/GO-KEGG/"},{"name":"org.At.tair.db","slug":"org-At-tair-db","permalink":"http://www.shelven.com/tags/org-At-tair-db/"},{"name":"ffmpeg","slug":"ffmpeg","permalink":"http://www.shelven.com/tags/ffmpeg/"},{"name":"numpy","slug":"numpy","permalink":"http://www.shelven.com/tags/numpy/"},{"name":"pillow","slug":"pillow","permalink":"http://www.shelven.com/tags/pillow/"},{"name":"R语言","slug":"R语言","permalink":"http://www.shelven.com/tags/R%E8%AF%AD%E8%A8%80/"},{"name":"pheatmap","slug":"pheatmap","permalink":"http://www.shelven.com/tags/pheatmap/"},{"name":"SRA","slug":"SRA","permalink":"http://www.shelven.com/tags/SRA/"},{"name":"SRA Toolkit","slug":"SRA-Toolkit","permalink":"http://www.shelven.com/tags/SRA-Toolkit/"},{"name":"GEO","slug":"GEO","permalink":"http://www.shelven.com/tags/GEO/"},{"name":"vscode","slug":"vscode","permalink":"http://www.shelven.com/tags/vscode/"},{"name":"ggplot2","slug":"ggplot2","permalink":"http://www.shelven.com/tags/ggplot2/"},{"name":"ggrepel","slug":"ggrepel","permalink":"http://www.shelven.com/tags/ggrepel/"},{"name":"linux指令","slug":"linux指令","permalink":"http://www.shelven.com/tags/linux%E6%8C%87%E4%BB%A4/"},{"name":"shell脚本","slug":"shell脚本","permalink":"http://www.shelven.com/tags/shell%E8%84%9A%E6%9C%AC/"},{"name":"DESeq2","slug":"DESeq2","permalink":"http://www.shelven.com/tags/DESeq2/"},{"name":"dplyr","slug":"dplyr","permalink":"http://www.shelven.com/tags/dplyr/"},{"name":"HTseq","slug":"HTseq","permalink":"http://www.shelven.com/tags/HTseq/"},{"name":"stringtie","slug":"stringtie","permalink":"http://www.shelven.com/tags/stringtie/"},{"name":"prepDE.py3","slug":"prepDE-py3","permalink":"http://www.shelven.com/tags/prepDE-py3/"},{"name":"IGV","slug":"IGV","permalink":"http://www.shelven.com/tags/IGV/"},{"name":"samtools","slug":"samtools","permalink":"http://www.shelven.com/tags/samtools/"},{"name":"hisat2","slug":"hisat2","permalink":"http://www.shelven.com/tags/hisat2/"},{"name":"fastqc","slug":"fastqc","permalink":"http://www.shelven.com/tags/fastqc/"},{"name":"multiqc","slug":"multiqc","permalink":"http://www.shelven.com/tags/multiqc/"},{"name":"trim-galore","slug":"trim-galore","permalink":"http://www.shelven.com/tags/trim-galore/"}]} \ No newline at end of file diff --git a/history/index.html b/history/index.html index 35fbe01e6e..18c6b366f0 100644 --- a/history/index.html +++ b/history/index.html @@ -22,7 +22,7 @@ - + @@ -1780,8 +1780,8 @@ } - - + + + @@ -3877,7 +3882,7 @@

Phantom

- + @@ -4007,19 +4012,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4088,7 +4093,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4208,7 +4213,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4548,7 +4553,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/index.html b/index.html index a8bf58ebf2..59ad7c7067 100644 --- a/index.html +++ b/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4495,7 +4502,7 @@

Phantom

- + @@ -4625,19 +4632,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4706,7 +4713,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4826,7 +4833,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -5166,7 +5173,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/more/404.html b/more/404.html index 11024c7527..40ee2b37e4 100644 --- a/more/404.html +++ b/more/404.html @@ -22,7 +22,7 @@ - + @@ -1780,8 +1780,8 @@ } - - + + + @@ -3519,7 +3519,7 @@ - + @@ -3649,19 +3649,19 @@ const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3730,7 +3730,7 @@ await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -3850,7 +3850,7 @@ function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4190,7 +4190,7 @@ var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/mylist/index.html b/mylist/index.html index 913db7ff1c..7e3788c498 100644 --- a/mylist/index.html +++ b/mylist/index.html @@ -22,7 +22,7 @@ - + @@ -1778,8 +1778,8 @@ } - - + + + @@ -3824,7 +3831,7 @@

Phantom

- + @@ -3954,19 +3961,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4035,7 +4042,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4155,7 +4162,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4495,7 +4502,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/page/2/index.html b/page/2/index.html index 0a3ecc07e6..4262a6ba1d 100644 --- a/page/2/index.html +++ b/page/2/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4340,7 +4345,7 @@

Phantom

- + @@ -4470,19 +4475,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4551,7 +4556,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4671,7 +4676,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -5011,7 +5016,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/page/3/index.html b/page/3/index.html index 8ebec997f5..0ad5ba42bd 100644 --- a/page/3/index.html +++ b/page/3/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4327,7 +4332,7 @@

Phantom

- + @@ -4457,19 +4462,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4538,7 +4543,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4658,7 +4663,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4998,7 +5003,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/page/4/index.html b/page/4/index.html index 767629fe58..8b533eb494 100644 --- a/page/4/index.html +++ b/page/4/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4323,7 +4332,7 @@

Phantom

- + @@ -4453,19 +4462,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4534,7 +4543,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4654,7 +4663,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4994,7 +5003,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/page/5/index.html b/page/5/index.html index 4f39759b96..bbc0ee4c58 100644 --- a/page/5/index.html +++ b/page/5/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4331,7 +4336,7 @@

Phantom

- + @@ -4461,19 +4466,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4542,7 +4547,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4662,7 +4667,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -5002,7 +5007,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/page/6/index.html b/page/6/index.html index 50c5a8826f..75870ddd7c 100644 --- a/page/6/index.html +++ b/page/6/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4327,7 +4328,7 @@

Phantom

- + @@ -4457,19 +4458,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4538,7 +4539,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4658,7 +4659,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4998,7 +4999,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/page/7/index.html b/page/7/index.html index fe6bf6dbaa..ba14497148 100644 --- a/page/7/index.html +++ b/page/7/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4012,7 +4082,7 @@

Phantom

- + @@ -4142,19 +4212,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4223,7 +4293,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4343,7 +4413,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4683,7 +4753,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/photo/index.html b/photo/index.html index d5fbc3664f..3a4e7d3453 100644 --- a/photo/index.html +++ b/photo/index.html @@ -22,7 +22,7 @@ - + @@ -1778,8 +1778,8 @@ } - - + + + @@ -3878,7 +3883,7 @@

Phantom

- + @@ -4008,19 +4013,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4089,7 +4094,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4209,7 +4214,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4549,7 +4554,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/3D-DNA/index.html b/tags/3D-DNA/index.html index 2b33a83d58..62d6887b2e 100644 --- a/tags/3D-DNA/index.html +++ b/tags/3D-DNA/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/ATAC-seq/index.html b/tags/ATAC-seq/index.html new file mode 100644 index 0000000000..2c2a07a8f9 --- /dev/null +++ b/tags/ATAC-seq/index.html @@ -0,0 +1,4750 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签:ATAC-seq - 我的小破站 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + +
+ +
+
+ + +
+ + + +
+ + + + + + + + + + +
+
+ + + + + + + + + + +

+ +

+ +
+ +

前阵子去兰州开全国植物生物学大会,做实验的师弟问了我一个问题:什么是ATAC-seq?因为王茂军老师课题组有同学在做CUT&Tag,我脑海里第一个出现的也是CUT&Tag,两者的研究内容虽然不同,但是使用的分析方式是非常相似的。正好看到菲沙基因做过这两个技术的讲座,这篇博客就整理一下介绍这两个技术以及衍生技术的分析原理,加深下自己的理解,详细的分析流程留到以后需要做的时候再记录。

+ +
+ +
+
+ + + + + 学习笔记表观遗传学 + + + + + + +
+ + + +
+ + +
+ +
+ + + +
+ + + + + + + +
+ + + + + + + + + +
+ + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + diff --git a/tags/AnnotationHub/index.html b/tags/AnnotationHub/index.html index b594e3b8a2..85af027497 100644 --- a/tags/AnnotationHub/index.html +++ b/tags/AnnotationHub/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/BLAST/index.html b/tags/BLAST/index.html index 941c8c7621..828191f389 100644 --- a/tags/BLAST/index.html +++ b/tags/BLAST/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/BUSCO/index.html b/tags/BUSCO/index.html index b85677e127..b96fb06356 100644 --- a/tags/BUSCO/index.html +++ b/tags/BUSCO/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/Barker3/index.html b/tags/Barker3/index.html index 949033b817..8214ed412c 100644 --- a/tags/Barker3/index.html +++ b/tags/Barker3/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/CUT-Tag/index.html b/tags/CUT-Tag/index.html new file mode 100644 index 0000000000..0c0e97a63b --- /dev/null +++ b/tags/CUT-Tag/index.html @@ -0,0 +1,4750 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标签:CUT&Tag - 我的小破站 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + +
+ +
+
+ + +
+ + + +
+ + + + + + + + + + +
+
+ + + + + + + + + + +

+ +

+ +
+ +

前阵子去兰州开全国植物生物学大会,做实验的师弟问了我一个问题:什么是ATAC-seq?因为王茂军老师课题组有同学在做CUT&Tag,我脑海里第一个出现的也是CUT&Tag,两者的研究内容虽然不同,但是使用的分析方式是非常相似的。正好看到菲沙基因做过这两个技术的讲座,这篇博客就整理一下介绍这两个技术以及衍生技术的分析原理,加深下自己的理解,详细的分析流程留到以后需要做的时候再记录。

+ +
+ +
+
+ + + + + 学习笔记表观遗传学 + + + + + + +
+ + + +
+ + +
+ +
+ + + +
+ + + + + + + +
+ + + + + + + + + +
+ + + + + + +
+
+
+ + + + + + + + + + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + diff --git a/tags/Canu/index.html b/tags/Canu/index.html index a6f25d8039..4e12ffb2d2 100644 --- a/tags/Canu/index.html +++ b/tags/Canu/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/ChatGPT/index.html b/tags/ChatGPT/index.html index 02f0d32165..83e23f371d 100644 --- a/tags/ChatGPT/index.html +++ b/tags/ChatGPT/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/DESeq2/index.html b/tags/DESeq2/index.html index 0cac38c0cf..0806b8955b 100644 --- a/tags/DESeq2/index.html +++ b/tags/DESeq2/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/GATK/index.html b/tags/GATK/index.html index d239100ac2..76476c3c2d 100644 --- a/tags/GATK/index.html +++ b/tags/GATK/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/GEO/index.html b/tags/GEO/index.html index d64586a876..c6acd9d0db 100644 --- a/tags/GEO/index.html +++ b/tags/GEO/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3747,7 +3752,7 @@

Phantom

- + @@ -3877,19 +3882,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3958,7 +3963,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4078,7 +4083,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4418,7 +4423,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/GMATA/index.html b/tags/GMATA/index.html index cac4ae6c08..f1d3057641 100644 --- a/tags/GMATA/index.html +++ b/tags/GMATA/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/GO-KEGG/index.html b/tags/GO-KEGG/index.html index 80c0b7ed03..a5d20b41b3 100644 --- a/tags/GO-KEGG/index.html +++ b/tags/GO-KEGG/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/GenomeScope2-0/index.html b/tags/GenomeScope2-0/index.html index f73c7a3912..04542cc773 100644 --- a/tags/GenomeScope2-0/index.html +++ b/tags/GenomeScope2-0/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/HTTP/index.html b/tags/HTTP/index.html index 8abd42c139..033b31d805 100644 --- a/tags/HTTP/index.html +++ b/tags/HTTP/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3747,7 +3752,7 @@

Phantom

- + @@ -3877,19 +3882,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3958,7 +3963,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4078,7 +4083,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4418,7 +4423,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/HTseq/index.html b/tags/HTseq/index.html index fe86e44e96..01674f87a2 100644 --- a/tags/HTseq/index.html +++ b/tags/HTseq/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/tags/Hi-C\346\237\223\350\211\262\344\275\223\346\214\202\350\275\275/index.html" "b/tags/Hi-C\346\237\223\350\211\262\344\275\223\346\214\202\350\275\275/index.html" index 519cf3a722..5323d8d5cf 100644 --- "a/tags/Hi-C\346\237\223\350\211\262\344\275\223\346\214\202\350\275\275/index.html" +++ "b/tags/Hi-C\346\237\223\350\211\262\344\275\223\346\214\202\350\275\275/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3818,7 +3823,7 @@

Phantom

- + @@ -3948,19 +3953,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4029,7 +4034,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4149,7 +4154,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4489,7 +4494,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/HiC-Pro/index.html b/tags/HiC-Pro/index.html index f8d512ca89..769e94aa41 100644 --- a/tags/HiC-Pro/index.html +++ b/tags/HiC-Pro/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/HiCPlotter/index.html b/tags/HiCPlotter/index.html index 9aea41c8df..1e79cfd618 100644 --- a/tags/HiCPlotter/index.html +++ b/tags/HiCPlotter/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/HiFiGAN/index.html b/tags/HiFiGAN/index.html index 1ee02821d0..f690a59f33 100644 --- a/tags/HiFiGAN/index.html +++ b/tags/HiFiGAN/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3747,7 +3752,7 @@

Phantom

- + @@ -3877,19 +3882,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3958,7 +3963,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4078,7 +4083,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4418,7 +4423,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/HiTC/index.html b/tags/HiTC/index.html index ec392d9518..dd7d907c28 100644 --- a/tags/HiTC/index.html +++ b/tags/HiTC/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/IGV/index.html b/tags/IGV/index.html index 6353cee744..b4c3780f24 100644 --- a/tags/IGV/index.html +++ b/tags/IGV/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/JBAT/index.html b/tags/JBAT/index.html index 6dfcfcc4f5..c68858fa2a 100644 --- a/tags/JBAT/index.html +++ b/tags/JBAT/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/LAI/index.html b/tags/LAI/index.html index 92abfd6a3a..4d39f1b7a5 100644 --- a/tags/LAI/index.html +++ b/tags/LAI/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/MCScanX/index.html b/tags/MCScanX/index.html index e1aad064c3..94e0b69552 100644 --- a/tags/MCScanX/index.html +++ b/tags/MCScanX/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/NextDenovo/index.html b/tags/NextDenovo/index.html index 45bc1499ca..c6abcefd80 100644 --- a/tags/NextDenovo/index.html +++ b/tags/NextDenovo/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/NextPolish/index.html b/tags/NextPolish/index.html index 74f71a76a9..b2fd731ef7 100644 --- a/tags/NextPolish/index.html +++ b/tags/NextPolish/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/Plink/index.html b/tags/Plink/index.html index 3c8fdc44a0..e4ecad1e23 100644 --- a/tags/Plink/index.html +++ b/tags/Plink/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/QUAST/index.html b/tags/QUAST/index.html index 219f0dd069..cccd4abcbe 100644 --- a/tags/QUAST/index.html +++ b/tags/QUAST/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/Racon/index.html b/tags/Racon/index.html index 1d3821e959..5752cf35b4 100644 --- a/tags/Racon/index.html +++ b/tags/Racon/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/RepeatMasker/index.html b/tags/RepeatMasker/index.html index b2e0ae6142..f9d369651b 100644 --- a/tags/RepeatMasker/index.html +++ b/tags/RepeatMasker/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/RepeatModeler/index.html b/tags/RepeatModeler/index.html index cb7ae1e514..aaae7b430a 100644 --- a/tags/RepeatModeler/index.html +++ b/tags/RepeatModeler/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/Rfam-Infernal/index.html b/tags/Rfam-Infernal/index.html index 6929134aa5..7b0b8ad77e 100644 --- a/tags/Rfam-Infernal/index.html +++ b/tags/Rfam-Infernal/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/tags/R\350\257\255\350\250\200/index.html" "b/tags/R\350\257\255\350\250\200/index.html" index fa4affc7d8..b515f84802 100644 --- "a/tags/R\350\257\255\350\250\200/index.html" +++ "b/tags/R\350\257\255\350\250\200/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3885,7 +3890,7 @@

Phantom

- + @@ -4015,19 +4020,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4096,7 +4101,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4216,7 +4221,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4556,7 +4561,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/SOAPdenovo2/index.html b/tags/SOAPdenovo2/index.html index 7d067613f2..e31a668272 100644 --- a/tags/SOAPdenovo2/index.html +++ b/tags/SOAPdenovo2/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/SRA-Toolkit/index.html b/tags/SRA-Toolkit/index.html index 1aa8bd66d8..0738e07422 100644 --- a/tags/SRA-Toolkit/index.html +++ b/tags/SRA-Toolkit/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3747,7 +3752,7 @@

Phantom

- + @@ -3877,19 +3882,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3958,7 +3963,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4078,7 +4083,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4418,7 +4423,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/SRA/index.html b/tags/SRA/index.html index 3163d0f492..5b690db2e0 100644 --- a/tags/SRA/index.html +++ b/tags/SRA/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3747,7 +3752,7 @@

Phantom

- + @@ -3877,19 +3882,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3958,7 +3963,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4078,7 +4083,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4418,7 +4423,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/Seurat/index.html b/tags/Seurat/index.html index 8e93f274d1..9bc41c49e3 100644 --- a/tags/Seurat/index.html +++ b/tags/Seurat/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3752,7 +3757,7 @@

Phantom

- + @@ -3882,19 +3887,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3963,7 +3968,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4083,7 +4088,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4423,7 +4428,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/TEsorter/index.html b/tags/TEsorter/index.html index 72789d8d14..9140c378b2 100644 --- a/tags/TEsorter/index.html +++ b/tags/TEsorter/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/TRF/index.html b/tags/TRF/index.html index 51e6cea2e6..d82fcaaee2 100644 --- a/tags/TRF/index.html +++ b/tags/TRF/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/Tacotron2/index.html b/tags/Tacotron2/index.html index 069a730a55..8b8094a872 100644 --- a/tags/Tacotron2/index.html +++ b/tags/Tacotron2/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3747,7 +3752,7 @@

Phantom

- + @@ -3877,19 +3882,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3958,7 +3963,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4078,7 +4083,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4418,7 +4423,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/Tassel5/index.html b/tags/Tassel5/index.html index 647a77ca6e..5c6c6644f2 100644 --- a/tags/Tassel5/index.html +++ b/tags/Tassel5/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/Xpath/index.html b/tags/Xpath/index.html index ef3a3f42c9..a007228554 100644 --- a/tags/Xpath/index.html +++ b/tags/Xpath/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3747,7 +3752,7 @@

Phantom

- + @@ -3877,19 +3882,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3958,7 +3963,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4078,7 +4083,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4418,7 +4423,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/aspera/index.html b/tags/aspera/index.html index 18c5e18cc0..48e2bb76d1 100644 --- a/tags/aspera/index.html +++ b/tags/aspera/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/bioperl/index.html b/tags/bioperl/index.html index 5aa5670766..a9aca48ace 100644 --- a/tags/bioperl/index.html +++ b/tags/bioperl/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3747,7 +3752,7 @@

Phantom

- + @@ -3877,19 +3882,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3958,7 +3963,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4078,7 +4083,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4418,7 +4423,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/blast/index.html b/tags/blast/index.html index 2eba6532d7..974ae5c9a2 100644 --- a/tags/blast/index.html +++ b/tags/blast/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3747,7 +3752,7 @@

Phantom

- + @@ -3877,19 +3882,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3958,7 +3963,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4078,7 +4083,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4418,7 +4423,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/dplyr/index.html b/tags/dplyr/index.html index fd272162d4..c17ca6def4 100644 --- a/tags/dplyr/index.html +++ b/tags/dplyr/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/fastqc/index.html b/tags/fastqc/index.html index e493c7c158..7ba92b4fb3 100644 --- a/tags/fastqc/index.html +++ b/tags/fastqc/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/ffmpeg/index.html b/tags/ffmpeg/index.html index 755e7e67fe..18e3584998 100644 --- a/tags/ffmpeg/index.html +++ b/tags/ffmpeg/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3747,7 +3752,7 @@

Phantom

- + @@ -3877,19 +3882,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3958,7 +3963,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4078,7 +4083,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4418,7 +4423,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/filtlong/index.html b/tags/filtlong/index.html index fc35481fca..0827b464de 100644 --- a/tags/filtlong/index.html +++ b/tags/filtlong/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/ggplot2/index.html b/tags/ggplot2/index.html index 2d712ba9ae..3911b5bcd8 100644 --- a/tags/ggplot2/index.html +++ b/tags/ggplot2/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/ggrepel/index.html b/tags/ggrepel/index.html index d44246fb8f..01a935be57 100644 --- a/tags/ggrepel/index.html +++ b/tags/ggrepel/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/github/index.html b/tags/github/index.html index 57a242243f..f44230ecb0 100644 --- a/tags/github/index.html +++ b/tags/github/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3747,7 +3752,7 @@

Phantom

- + @@ -3877,19 +3882,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3958,7 +3963,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4078,7 +4083,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4418,7 +4423,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/go-cqhttp/index.html b/tags/go-cqhttp/index.html index 6da9ed9795..a5a2e1a71d 100644 --- a/tags/go-cqhttp/index.html +++ b/tags/go-cqhttp/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3810,7 +3815,7 @@

Phantom

- + @@ -3940,19 +3945,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4021,7 +4026,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4141,7 +4146,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4481,7 +4486,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/google-colab/index.html b/tags/google-colab/index.html index 53b658766e..6bc1c897ba 100644 --- a/tags/google-colab/index.html +++ b/tags/google-colab/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3747,7 +3752,7 @@

Phantom

- + @@ -3877,19 +3882,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3958,7 +3963,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4078,7 +4083,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4418,7 +4423,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/hisat2/index.html b/tags/hisat2/index.html index c294ddafe3..95ef50eacc 100644 --- a/tags/hisat2/index.html +++ b/tags/hisat2/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/index.html b/tags/index.html index dcaf97e88a..cafdd16d9d 100644 --- a/tags/index.html +++ b/tags/index.html @@ -22,7 +22,7 @@ - + @@ -1780,8 +1780,8 @@ } - - + + + @@ -3690,7 +3695,7 @@

Phantom

- + @@ -3820,19 +3825,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3901,7 +3906,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4021,7 +4026,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4361,7 +4366,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/jellyfish/index.html b/tags/jellyfish/index.html index fc76317cf9..7e3a79a931 100644 --- a/tags/jellyfish/index.html +++ b/tags/jellyfish/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/juicer2/index.html b/tags/juicer2/index.html index f5e427857a..6dffb8062a 100644 --- a/tags/juicer2/index.html +++ b/tags/juicer2/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/kmergenie/index.html b/tags/kmergenie/index.html index 1290e375d0..264295c3b6 100644 --- a/tags/kmergenie/index.html +++ b/tags/kmergenie/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/tags/linux\346\214\207\344\273\244/index.html" "b/tags/linux\346\214\207\344\273\244/index.html" index ff4fedb0c6..bbc5da6f34 100644 --- "a/tags/linux\346\214\207\344\273\244/index.html" +++ "b/tags/linux\346\214\207\344\273\244/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3747,7 +3752,7 @@

Phantom

- + @@ -3877,19 +3882,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3958,7 +3963,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4078,7 +4083,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4418,7 +4423,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/multiqc/index.html b/tags/multiqc/index.html index 34acd0243d..5d37c8672e 100644 --- a/tags/multiqc/index.html +++ b/tags/multiqc/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/nanoplot/index.html b/tags/nanoplot/index.html index ca1130d224..ce2a51e0b5 100644 --- a/tags/nanoplot/index.html +++ b/tags/nanoplot/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/numpy/index.html b/tags/numpy/index.html index f8796a0cc5..f671c15faf 100644 --- a/tags/numpy/index.html +++ b/tags/numpy/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3747,7 +3752,7 @@

Phantom

- + @@ -3877,19 +3882,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3958,7 +3963,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4078,7 +4083,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4418,7 +4423,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/org-At-tair-db/index.html b/tags/org-At-tair-db/index.html index c4749fe0c6..df0ac0e4a2 100644 --- a/tags/org-At-tair-db/index.html +++ b/tags/org-At-tair-db/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/perl/index.html b/tags/perl/index.html index ef63a5530d..f16252740b 100644 --- a/tags/perl/index.html +++ b/tags/perl/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3810,7 +3815,7 @@

Phantom

- + @@ -3940,19 +3945,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4021,7 +4026,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4141,7 +4146,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4481,7 +4486,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/pheatmap/index.html b/tags/pheatmap/index.html index 53f0119a12..b7363a58c4 100644 --- a/tags/pheatmap/index.html +++ b/tags/pheatmap/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/pillow/index.html b/tags/pillow/index.html index fb21202ac9..07a5d8f053 100644 --- a/tags/pillow/index.html +++ b/tags/pillow/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3747,7 +3752,7 @@

Phantom

- + @@ -3877,19 +3882,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3958,7 +3963,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4078,7 +4083,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4418,7 +4423,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/prepDE-py3/index.html b/tags/prepDE-py3/index.html index e271370ec8..a2a1bd8035 100644 --- a/tags/prepDE-py3/index.html +++ b/tags/prepDE-py3/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/python/index.html b/tags/python/index.html index aa77920549..909a3f1eec 100644 --- a/tags/python/index.html +++ b/tags/python/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -4365,7 +4370,7 @@

Phantom

- + @@ -4495,19 +4500,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4576,7 +4581,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4696,7 +4701,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -5036,7 +5041,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/python/page/2/index.html b/tags/python/page/2/index.html index 7e68410d85..eebd09ec8c 100644 --- a/tags/python/page/2/index.html +++ b/tags/python/page/2/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3878,7 +3883,7 @@

Phantom

- + @@ -4008,19 +4013,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4089,7 +4094,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4209,7 +4214,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4549,7 +4554,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/qq-bot/index.html b/tags/qq-bot/index.html index da394527b8..1da3459451 100644 --- a/tags/qq-bot/index.html +++ b/tags/qq-bot/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3810,7 +3815,7 @@

Phantom

- + @@ -3940,19 +3945,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4021,7 +4026,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4141,7 +4146,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4481,7 +4486,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/requests/index.html b/tags/requests/index.html index 107f1886b1..a642be1eba 100644 --- a/tags/requests/index.html +++ b/tags/requests/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3747,7 +3752,7 @@

Phantom

- + @@ -3877,19 +3882,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3958,7 +3963,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4078,7 +4083,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4418,7 +4423,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/samtools/index.html b/tags/samtools/index.html index 6921eadd19..046c3effe4 100644 --- a/tags/samtools/index.html +++ b/tags/samtools/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/selenium/index.html b/tags/selenium/index.html index 0016324908..61cdcbb624 100644 --- a/tags/selenium/index.html +++ b/tags/selenium/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3747,7 +3752,7 @@

Phantom

- + @@ -3877,19 +3882,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3958,7 +3963,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4078,7 +4083,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4418,7 +4423,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/tags/shell\350\204\232\346\234\254/index.html" "b/tags/shell\350\204\232\346\234\254/index.html" index b0e6db14d5..66d1842ccf 100644 --- "a/tags/shell\350\204\232\346\234\254/index.html" +++ "b/tags/shell\350\204\232\346\234\254/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3747,7 +3752,7 @@

Phantom

- + @@ -3877,19 +3882,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3958,7 +3963,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4078,7 +4083,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4418,7 +4423,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/singularity/index.html b/tags/singularity/index.html index 6a4ea9e618..ff1ce737f8 100644 --- a/tags/singularity/index.html +++ b/tags/singularity/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3747,7 +3752,7 @@

Phantom

- + @@ -3877,19 +3882,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3958,7 +3963,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4078,7 +4083,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4418,7 +4423,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/slurm/index.html b/tags/slurm/index.html index 8939ee0e66..b01ccc62f3 100644 --- a/tags/slurm/index.html +++ b/tags/slurm/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3747,7 +3752,7 @@

Phantom

- + @@ -3877,19 +3882,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3958,7 +3963,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4078,7 +4083,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4418,7 +4423,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/stringtie/index.html b/tags/stringtie/index.html index 8690ee3aa7..02b66034bb 100644 --- a/tags/stringtie/index.html +++ b/tags/stringtie/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/tRNAscan-SE/index.html b/tags/tRNAscan-SE/index.html index cc49432809..c62e530455 100644 --- a/tags/tRNAscan-SE/index.html +++ b/tags/tRNAscan-SE/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/trim-galore/index.html b/tags/trim-galore/index.html index 9c503bb2b7..fbfc74f66f 100644 --- a/tags/trim-galore/index.html +++ b/tags/trim-galore/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/tags/vscode/index.html b/tags/vscode/index.html index fe52cec8e4..6f4919dec2 100644 --- a/tags/vscode/index.html +++ b/tags/vscode/index.html @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3747,7 +3752,7 @@

Phantom

- + @@ -3877,19 +3882,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3958,7 +3963,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4078,7 +4083,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4418,7 +4423,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/tags/\344\272\272\345\267\245\346\231\272\350\203\275/index.html" "b/tags/\344\272\272\345\267\245\346\231\272\350\203\275/index.html" index 638e1d83b8..d755272020 100644 --- "a/tags/\344\272\272\345\267\245\346\231\272\350\203\275/index.html" +++ "b/tags/\344\272\272\345\267\245\346\231\272\350\203\275/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3747,7 +3752,7 @@

Phantom

- + @@ -3877,19 +3882,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3958,7 +3963,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4078,7 +4083,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4418,7 +4423,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/tags/\344\273\243\347\220\206\346\234\215\345\212\241\345\231\250/index.html" "b/tags/\344\273\243\347\220\206\346\234\215\345\212\241\345\231\250/index.html" index eeda4eb599..4552c8921c 100644 --- "a/tags/\344\273\243\347\220\206\346\234\215\345\212\241\345\231\250/index.html" +++ "b/tags/\344\273\243\347\220\206\346\234\215\345\212\241\345\231\250/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3747,7 +3752,7 @@

Phantom

- + @@ -3877,19 +3882,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3958,7 +3963,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4078,7 +4083,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4418,7 +4423,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/tags/\345\205\261\347\272\277\346\200\247\345\210\206\346\236\220/index.html" "b/tags/\345\205\261\347\272\277\346\200\247\345\210\206\346\236\220/index.html" index 29ed23a368..dc189cd575 100644 --- "a/tags/\345\205\261\347\272\277\346\200\247\345\210\206\346\236\220/index.html" +++ "b/tags/\345\205\261\347\272\277\346\200\247\345\210\206\346\236\220/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3751,7 +3756,7 @@

Phantom

- + @@ -3881,19 +3886,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3962,7 +3967,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4082,7 +4087,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4422,7 +4427,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/tags/\345\206\205\347\275\221\347\251\277\351\200\217/index.html" "b/tags/\345\206\205\347\275\221\347\251\277\351\200\217/index.html" index cec1276d04..d42fef2109 100644 --- "a/tags/\345\206\205\347\275\221\347\251\277\351\200\217/index.html" +++ "b/tags/\345\206\205\347\275\221\347\251\277\351\200\217/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3810,7 +3815,7 @@

Phantom

- + @@ -3940,19 +3945,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4021,7 +4026,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4141,7 +4146,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4481,7 +4486,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/tags/\345\217\215\345\220\221\344\273\243\347\220\206/index.html" "b/tags/\345\217\215\345\220\221\344\273\243\347\220\206/index.html" index d7603138ed..35a65cbd58 100644 --- "a/tags/\345\217\215\345\220\221\344\273\243\347\220\206/index.html" +++ "b/tags/\345\217\215\345\220\221\344\273\243\347\220\206/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3747,7 +3752,7 @@

Phantom

- + @@ -3877,19 +3882,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3958,7 +3963,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4078,7 +4083,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4418,7 +4423,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/tags/\345\243\260\347\272\271\350\257\206\345\210\253/index.html" "b/tags/\345\243\260\347\272\271\350\257\206\345\210\253/index.html" index 75ffbc3814..2e835649e5 100644 --- "a/tags/\345\243\260\347\272\271\350\257\206\345\210\253/index.html" +++ "b/tags/\345\243\260\347\272\271\350\257\206\345\210\253/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3747,7 +3752,7 @@

Phantom

- + @@ -3877,19 +3882,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3958,7 +3963,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4078,7 +4083,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4418,7 +4423,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/tags/\345\256\271\345\231\250/index.html" "b/tags/\345\256\271\345\231\250/index.html" index 57925bf153..9e954b2ccc 100644 --- "a/tags/\345\256\271\345\231\250/index.html" +++ "b/tags/\345\256\271\345\231\250/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3747,7 +3752,7 @@

Phantom

- + @@ -3877,19 +3882,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3958,7 +3963,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4078,7 +4083,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4418,7 +4423,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/tags/\345\273\272\347\253\231/index.html" "b/tags/\345\273\272\347\253\231/index.html" index 85d4ee31f6..819af26506 100644 --- "a/tags/\345\273\272\347\253\231/index.html" +++ "b/tags/\345\273\272\347\253\231/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3811,7 +3816,7 @@

Phantom

- + @@ -3941,19 +3946,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4022,7 +4027,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4142,7 +4147,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4482,7 +4487,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/tags/\346\213\206\345\214\205/index.html" "b/tags/\346\213\206\345\214\205/index.html" index aaf904b7ce..e8ed5b7f93 100644 --- "a/tags/\346\213\206\345\214\205/index.html" +++ "b/tags/\346\213\206\345\214\205/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3747,7 +3752,7 @@

Phantom

- + @@ -3877,19 +3882,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3958,7 +3963,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4078,7 +4083,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4418,7 +4423,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/tags/\347\210\254\350\231\253/index.html" "b/tags/\347\210\254\350\231\253/index.html" index e9821a1ccc..576275354e 100644 --- "a/tags/\347\210\254\350\231\253/index.html" +++ "b/tags/\347\210\254\350\231\253/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3936,7 +3941,7 @@

Phantom

- + @@ -4066,19 +4071,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4147,7 +4152,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4267,7 +4272,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4607,7 +4612,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/tags/\347\276\244\344\275\223\345\237\272\345\233\240\347\273\204\345\255\246/index.html" "b/tags/\347\276\244\344\275\223\345\237\272\345\233\240\347\273\204\345\255\246/index.html" index 62b2829ace..b5e458dc78 100644 --- "a/tags/\347\276\244\344\275\223\345\237\272\345\233\240\347\273\204\345\255\246/index.html" +++ "b/tags/\347\276\244\344\275\223\345\237\272\345\233\240\347\273\204\345\255\246/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3818,7 +3823,7 @@

Phantom

- + @@ -3948,19 +3953,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4029,7 +4034,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4149,7 +4154,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4489,7 +4494,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/tags/\350\257\255\351\237\263\350\275\254\346\226\207\346\234\254/index.html" "b/tags/\350\257\255\351\237\263\350\275\254\346\226\207\346\234\254/index.html" index 087e2a7d27..ede66e1c9f 100644 --- "a/tags/\350\257\255\351\237\263\350\275\254\346\226\207\346\234\254/index.html" +++ "b/tags/\350\257\255\351\237\263\350\275\254\346\226\207\346\234\254/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3747,7 +3752,7 @@

Phantom

- + @@ -3877,19 +3882,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3958,7 +3963,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4078,7 +4083,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4418,7 +4423,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git "a/tags/\351\233\206\347\276\244/index.html" "b/tags/\351\233\206\347\276\244/index.html" index ffa6bb15f5..c762e05bf8 100644 --- "a/tags/\351\233\206\347\276\244/index.html" +++ "b/tags/\351\233\206\347\276\244/index.html" @@ -22,7 +22,7 @@ - + @@ -1776,8 +1776,8 @@ } - - + + + @@ -3747,7 +3752,7 @@

Phantom

- + @@ -3877,19 +3882,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -3958,7 +3963,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4078,7 +4083,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4418,7 +4423,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{ diff --git a/weibo/index.html b/weibo/index.html index 8f07e8acfa..d90344170e 100644 --- a/weibo/index.html +++ b/weibo/index.html @@ -22,7 +22,7 @@ - + @@ -1778,8 +1778,8 @@ } - - + + + @@ -3877,7 +3882,7 @@

Phantom

- + @@ -4007,19 +4012,19 @@

Phantom

const sites_api = document.getElementById('sites-api'); if (sites_api != undefined && typeof SitesJS === 'undefined') { - volantis.js("/js/plugins/sites.js?time=1691161713030") + volantis.js("/js/plugins/sites.js?time=1692289455290") } const friends_api = document.getElementById('friends-api'); if (friends_api != undefined && typeof FriendsJS === 'undefined') { - volantis.js("/js/plugins/friends.js?time=1691161713030") + volantis.js("/js/plugins/friends.js?time=1692289455290") } const contributors_api = document.getElementById('contributors-api'); if (contributors_api != undefined && typeof ContributorsJS === 'undefined') { - volantis.js("/js/plugins/contributors.js?time=1691161713030") + volantis.js("/js/plugins/contributors.js?time=1692289455290") } }; @@ -4088,7 +4093,7 @@

Phantom

await volantis.js("/libs/meting/dist/Meting.min.js") // 右键 music 需要在 APlayer MetingJS 之后加载 - await volantis.js('/js/plugins/aplayer.js?time=1691161713030') + await volantis.js('/js/plugins/aplayer.js?time=1692289455290') })(); @@ -4208,7 +4213,7 @@

Phantom

function loadSearchScript() { // see: layout/_partial/scripts/_ctrl/cdnCtrl.ejs - return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1691161713030"); + return volantis.js("https://cdn.jsdelivr.net/gh/Phantom-Aria/Phantom-Aria.github.io@master/js/search/hexo.js?time=1692289455290"); } function loadSearchService() { @@ -4548,7 +4553,7 @@

Phantom

var runningOnBrowser = typeof window !== "undefined"; var isBot = runningOnBrowser && !("onscroll" in window) || typeof navigator !== "undefined" && /(gle|ing|ro|msn)bot|crawl|spider|yand|duckgo/i.test(navigator.userAgent); if (!isBot) { - volantis.js('/js/plugins/parallax.js?time=1691161713030').then(()=>{ + volantis.js('/js/plugins/parallax.js?time=1692289455290').then(()=>{ parallax() }) volantis.pjax.send(()=>{